From 89b52d8126ac68d6fc816ac76e3290423a0a5116 Mon Sep 17 00:00:00 2001 From: 0utplay Date: Tue, 27 Jan 2026 21:56:18 +0100 Subject: [PATCH 01/13] feat: new sql module --- .../modules/mongodb/impl/MongoDBDatabase.java | 6 - .../modules/mysql/config/DatabaseType.java | 25 +++ .../mysql/config/JooqConfigurationEntry.java | 55 ++++++ modules/database-mysql/impl/build.gradle.kts | 5 + .../impl/CloudNetMySQLDatabaseModule.java | 2 +- .../modules/mysql/impl/DocumentConverter.java | 51 +++++ .../modules/mysql/impl/JooqDatabase.java | 170 ++++++++++++++++ .../modules/mysql/impl/JooqDatabaseType.java | 59 ++++++ .../modules/mysql/impl/JooqProvider.java | 97 +++++++++ .../modules/mysql/impl/MySQLDatabase.java | 16 -- .../mysql/impl/MySQLDatabaseProvider.java | 6 +- .../modules/mysql/impl/JooqDatabaseTest.java | 184 ++++++++++++++++++ .../modules/mysql/impl/MySQLDatabaseTest.java | 3 +- .../node/database/LocalDatabase.java | 8 - .../node/impl/database/h2/H2Database.java | 16 -- .../impl/database/xodus/XodusDatabase.java | 5 - 16 files changed, 649 insertions(+), 59 deletions(-) create mode 100644 modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/DatabaseType.java create mode 100644 modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/JooqConfigurationEntry.java create mode 100644 modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/DocumentConverter.java create mode 100644 modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabase.java create mode 100644 modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseType.java create mode 100644 modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqProvider.java create mode 100644 modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseTest.java diff --git a/modules/database-mongodb/impl/src/main/java/eu/cloudnetservice/modules/mongodb/impl/MongoDBDatabase.java b/modules/database-mongodb/impl/src/main/java/eu/cloudnetservice/modules/mongodb/impl/MongoDBDatabase.java index 5a018ebaf5..4ab24480b2 100644 --- a/modules/database-mongodb/impl/src/main/java/eu/cloudnetservice/modules/mongodb/impl/MongoDBDatabase.java +++ b/modules/database-mongodb/impl/src/main/java/eu/cloudnetservice/modules/mongodb/impl/MongoDBDatabase.java @@ -31,7 +31,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.BiConsumer; import lombok.NonNull; import org.bson.conversions.Bson; import org.jetbrains.annotations.Nullable; @@ -169,11 +168,6 @@ public boolean delete(@NonNull String key) { return entries; } - @Override - public void iterate(@NonNull BiConsumer consumer) { - this.entries().forEach(consumer); - } - @Override public void clear() { this.collection.deleteMany(new org.bson.Document()); diff --git a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/DatabaseType.java b/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/DatabaseType.java new file mode 100644 index 0000000000..419d73311b --- /dev/null +++ b/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/DatabaseType.java @@ -0,0 +1,25 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.mysql.config; + +public enum DatabaseType { + + POSTGRES, + MYSQL, + MARIADB, + SQLITE +} diff --git a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/JooqConfigurationEntry.java b/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/JooqConfigurationEntry.java new file mode 100644 index 0000000000..b9870912b7 --- /dev/null +++ b/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/JooqConfigurationEntry.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.mysql.config; + +import eu.cloudnetservice.driver.network.HostAndPort; +import java.util.Objects; +import lombok.NonNull; +import org.jetbrains.annotations.Nullable; + +public record JooqConfigurationEntry( + @NonNull DatabaseType databaseType, + @NonNull String databaseServiceName, + @Nullable String databaseName, + @Nullable String username, + @Nullable String password, + @Nullable HostAndPort address, + @Nullable String overrideConnectionUri +) { + + private static final String JDBC_URI = "jdbc:%s://%s:%d/%s"; + + public @NonNull String buildConnectionUri() { + if (this.overrideConnectionUri != null && !this.overrideConnectionUri.isBlank()) { + return this.overrideConnectionUri; + } + + Objects.requireNonNull(this.address, ""); + Objects.requireNonNull(this.username, ""); + Objects.requireNonNull(this.password, ""); + Objects.requireNonNull(this.databaseName, ""); + var jdbcDescriptor = switch (this.databaseType) { + case MYSQL -> "mysql"; + case MARIADB -> "mariadb"; + case POSTGRES -> "postgresql"; + case SQLITE -> "sqlite"; + }; + + return String.format(JDBC_URI, jdbcDescriptor, this.address.host(), this.address.port(), this.databaseName); + } + +} diff --git a/modules/database-mysql/impl/build.gradle.kts b/modules/database-mysql/impl/build.gradle.kts index 96c51ba558..c36eb4005c 100644 --- a/modules/database-mysql/impl/build.gradle.kts +++ b/modules/database-mysql/impl/build.gradle.kts @@ -30,6 +30,11 @@ dependencies { compileOnly(libs.caffeine) compileOnlyApi(projects.node.nodeImpl) api(projects.modules.databaseMysql.databaseMysqlApi) + + implementation("org.jooq:jooq:3.20.10") + implementation("org.postgresql:postgresql:42.7.9") + implementation("com.mysql:mysql-connector-j:9.5.0") + implementation("org.mariadb.jdbc:mariadb-java-client:3.5.7") } tasks.shadowJar.configure { diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/CloudNetMySQLDatabaseModule.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/CloudNetMySQLDatabaseModule.java index 82765430c1..d5b7aead6e 100644 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/CloudNetMySQLDatabaseModule.java +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/CloudNetMySQLDatabaseModule.java @@ -64,7 +64,7 @@ public void registerDatabaseProvider(@NonNull ServiceRegistry serviceRegistry) { serviceRegistry.registerProvider( NodeDatabaseProvider.class, this.configuration.databaseServiceName(), - new MySQLDatabaseProvider(this.configuration, null)); + new MySQLDatabaseProvider(this.configuration)); } @ModuleTask(order = 127, lifecycle = ModuleLifeCycle.STOPPED) diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/DocumentConverter.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/DocumentConverter.java new file mode 100644 index 0000000000..eaf22db511 --- /dev/null +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/DocumentConverter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.mysql.impl; + +import eu.cloudnetservice.driver.document.Document; +import eu.cloudnetservice.driver.document.DocumentFactory; +import org.jetbrains.annotations.NotNull; +import org.jooq.Converter; +import org.jooq.JSONB; + +public class DocumentConverter implements Converter { + + public static final DocumentConverter INSTANCE = new DocumentConverter(); + + private DocumentConverter() { + } + + @Override + public Document from(JSONB databaseObject) { + return DocumentFactory.json().parse(databaseObject.data()); + } + + @Override + public JSONB to(Document userObject) { + return JSONB.valueOf(userObject.toString()); + } + + @Override + public @NotNull Class fromType() { + return JSONB.class; + } + + @Override + public @NotNull Class toType() { + return Document.class; + } +} diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabase.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabase.java new file mode 100644 index 0000000000..298c7bac09 --- /dev/null +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabase.java @@ -0,0 +1,170 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.mysql.impl; + +import eu.cloudnetservice.driver.document.Document; +import eu.cloudnetservice.node.impl.database.AbstractDatabase; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.NonNull; +import org.jetbrains.annotations.Nullable; +import org.jooq.Condition; +import org.jooq.DSLContext; +import org.jooq.Field; +import org.jooq.JSONB; +import org.jooq.Name; +import org.jooq.Record; +import org.jooq.Record1; +import org.jooq.Table; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; + +public class JooqDatabase extends AbstractDatabase { + + private static final Name KEY_FIELD_NAME = DSL.name("Name"); + private static final Field KEY_FIELD = DSL.field(KEY_FIELD_NAME, String.class); + private static final Name DOCUMENT_FIELD_NAME = DSL.name("Document"); + private static final Field DOCUMENT_FIELD = DSL + .field(DOCUMENT_FIELD_NAME, JSONB.class) + .convert(DocumentConverter.INSTANCE); + + protected final Name dslName; + protected final Table dslTable; + protected final DSLContext dslContext; + + protected JooqDatabase( + @NonNull String name, + @NonNull JooqProvider databaseProvider, + @NonNull DSLContext dslContext + ) { + super(name, databaseProvider); + + this.dslName = DSL.name(name); + this.dslTable = DSL.table(this.dslName); + this.dslContext = dslContext; + + this.dslContext.createTableIfNotExists(this.dslTable) + .column(KEY_FIELD, SQLDataType.VARCHAR(512).notNull()) + .column(DOCUMENT_FIELD, SQLDataType.JSONB.notNull()) + .primaryKey(KEY_FIELD) + .execute(); + } + + @Override + public @Nullable Map readChunk(long beginIndex, int chunkSize) { + return this.dslContext.select(DSL.asterisk()) + .from(this.dslTable) + .limit(chunkSize) + .offset(beginIndex) + .fetchMap(KEY_FIELD, DOCUMENT_FIELD); + } + + @Override + public boolean insert(@NonNull String key, @NonNull Document document) { + return this.dslContext.insertInto(this.dslTable) + .set(KEY_FIELD, key) + .set(DOCUMENT_FIELD, document) + .onDuplicateKeyUpdate() + .set(DOCUMENT_FIELD, document) + .execute() > 0; + } + + @Override + public boolean contains(@NonNull String key) { + return this.dslContext.fetchExists(this.dslContext.selectOne().from(this.dslTable).where(KEY_FIELD.eq(key))); + } + + @Override + public boolean delete(@NonNull String key) { + return this.dslContext.delete(this.dslTable).where(KEY_FIELD.eq(key)).execute() > 0; + } + + @Override + public @Nullable Document get(@NonNull String key) { + return this.dslContext + .select(DOCUMENT_FIELD) + .from(this.dslTable) + .where(KEY_FIELD.eq(key)) + .fetchOptional() + .map(Record1::value1) + .orElse(null); + } + + @Override + public @NonNull Collection find(@NonNull String fieldName, @Nullable String fieldValue) { + Map filters = HashMap.newHashMap(1); + filters.put(fieldName, fieldValue); + return this.find(filters); + } + + @Override + public @NonNull Collection find(@NonNull Map filters) { + List conditions = new ArrayList<>(); + for (var entry : filters.entrySet()) { + var jsonAttribute = DSL.jsonbGetAttributeAsText(DSL.field(DOCUMENT_FIELD_NAME, JSONB.class), entry.getKey()); + conditions.add(jsonAttribute.eq(entry.getValue())); + } + + return this.dslContext + .select(DOCUMENT_FIELD) + .from(this.dslTable) + .where(conditions) + .fetch() + .getValues(DOCUMENT_FIELD); + } + + @Override + public @NonNull Collection keys() { + return this.dslContext + .select(KEY_FIELD) + .from(this.dslName) + .fetch() + .getValues(KEY_FIELD); + } + + @Override + public @NonNull Collection documents() { + return this.dslContext.select(DOCUMENT_FIELD).from(this.dslTable).fetch().getValues(DOCUMENT_FIELD); + } + + @Override + public @NonNull Map entries() { + return this.dslContext.select(DSL.asterisk()).from(this.dslTable).fetchMap(KEY_FIELD, DOCUMENT_FIELD); + } + + @Override + public void clear() { + this.dslContext.truncate(this.name).execute(); + } + + @Override + public long documentCount() { + return this.dslContext.fetchCount(DSL.table(this.dslName)); + } + + @Override + public boolean synced() { + return true; + } + + @Override + public void close() { + } +} diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseType.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseType.java new file mode 100644 index 0000000000..36e9c38e8e --- /dev/null +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseType.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.mysql.impl; + +import eu.cloudnetservice.modules.mysql.config.DatabaseType; +import lombok.NonNull; +import org.jooq.SQLDialect; + +public enum JooqDatabaseType { + MYSQL("mysql", "com.mysql.cj.jdbc.Driver", SQLDialect.MYSQL), + MARIADB("mariadb", "org.mariadb.jdbc.Driver", SQLDialect.MARIADB), + POSTGRESQL("postgresql", "org.postgresql.Driver", SQLDialect.POSTGRES), + SQLITE("file", "org.sqlite.JDBC", SQLDialect.SQLITE); + + private final String jdbcDescriptor; + private final String driverClassName; + private final SQLDialect jooqDialect; + + JooqDatabaseType(@NonNull String jdbcDescriptor, @NonNull String driverClassName, @NonNull SQLDialect jooqDialect) { + this.jdbcDescriptor = jdbcDescriptor; + this.driverClassName = driverClassName; + this.jooqDialect = jooqDialect; + } + + public static @NonNull JooqDatabaseType fromDatabaseType(@NonNull DatabaseType databaseType) { + return switch (databaseType) { + case MYSQL -> MYSQL; + case MARIADB -> MARIADB; + case POSTGRES -> POSTGRESQL; + case SQLITE -> SQLITE; + }; + } + + public @NonNull String jdbcDescriptor() { + return this.jdbcDescriptor; + } + + public @NonNull String driverClassName() { + return this.driverClassName; + } + + public @NonNull SQLDialect jooqDialect() { + return this.jooqDialect; + } +} diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqProvider.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqProvider.java new file mode 100644 index 0000000000..e8759566c6 --- /dev/null +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqProvider.java @@ -0,0 +1,97 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.mysql.impl; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.node.database.LocalDatabase; +import eu.cloudnetservice.node.impl.database.AbstractNodeDatabaseProvider; +import java.util.Collection; +import lombok.NonNull; +import org.jooq.DSLContext; +import org.jooq.Named; +import org.jooq.impl.DSL; + +public class JooqProvider extends AbstractNodeDatabaseProvider { + + private final JooqConfigurationEntry config; + private DSLContext dslContext; + + protected JooqProvider(@NonNull JooqConfigurationEntry config) { + super(DEFAULT_REMOVAL_LISTENER); + + this.config = config; + } + + @Override + public boolean init() { + var hikariConfig = new HikariConfig(); + var endpoint = this.config.buildConnectionUri(); + + var databaseType = JooqDatabaseType.fromDatabaseType(this.config.databaseType()); + + hikariConfig.setJdbcUrl(endpoint); + hikariConfig.setDriverClassName(databaseType.driverClassName()); + hikariConfig.setUsername(this.config.username()); + hikariConfig.setPassword(this.config.password()); + + hikariConfig.addDataSourceProperty("cachePrepStmts", "true"); + hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250"); + hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); + hikariConfig.addDataSourceProperty("useServerPrepStmts", "true"); + hikariConfig.addDataSourceProperty("useLocalSessionState", "true"); + hikariConfig.addDataSourceProperty("rewriteBatchedStatements", "true"); + hikariConfig.addDataSourceProperty("cacheResultSetMetadata", "true"); + hikariConfig.addDataSourceProperty("cacheServerConfiguration", "true"); + hikariConfig.addDataSourceProperty("elideSetAutoCommits", "true"); + hikariConfig.addDataSourceProperty("maintainTimeStats", "false"); + + hikariConfig.setMinimumIdle(2); + hikariConfig.setMaximumPoolSize(100); + hikariConfig.setConnectionTimeout(10_000); + hikariConfig.setValidationTimeout(10_000); + + this.dslContext = DSL.using(new HikariDataSource(hikariConfig), databaseType.jooqDialect()); + return true; + } + + @Override + public @NonNull LocalDatabase database(@NonNull String name) { + return new JooqDatabase(name, this, this.dslContext); + } + + @Override + public boolean containsDatabase(@NonNull String name) { + return this.databaseNames().stream().anyMatch(dbName -> dbName.equalsIgnoreCase(name)); + } + + @Override + public boolean deleteDatabase(@NonNull String name) { + return this.dslContext.dropTableIfExists(name).execute() != -1; + } + + @Override + public @NonNull Collection databaseNames() { + return this.dslContext.meta().getTables().stream().map(Named::getName).toList(); + } + + @Override + public @NonNull String name() { + return this.config.databaseServiceName(); + } +} diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabase.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabase.java index 5fae2748e9..1c9cf13b74 100644 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabase.java +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabase.java @@ -28,7 +28,6 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.BiConsumer; import lombok.NonNull; import org.jetbrains.annotations.Nullable; @@ -208,21 +207,6 @@ public boolean synced() { return true; } - @Override - public void iterate(@NonNull BiConsumer consumer) { - this.databaseProvider.executeQuery( - String.format("SELECT * FROM `%s`;", this.name), - resultSet -> { - while (resultSet.next()) { - var key = resultSet.getString(TABLE_COLUMN_KEY); - var document = DocumentFactory.json().parse(resultSet.getString(TABLE_COLUMN_VAL)); - consumer.accept(key, document); - } - - return null; - }, null); - } - @Override public @Nullable Map readChunk(long beginIndex, int chunkSize) { return this.databaseProvider.executeQuery( diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseProvider.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseProvider.java index 41d1c9cece..4bff442687 100644 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseProvider.java +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseProvider.java @@ -28,7 +28,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Set; -import java.util.concurrent.ExecutorService; import lombok.NonNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; @@ -40,10 +39,7 @@ public final class MySQLDatabaseProvider extends SQLDatabaseProvider { private final MySQLConfiguration config; private volatile HikariDataSource hikariDataSource; - public MySQLDatabaseProvider( - @NonNull MySQLConfiguration config, - @Nullable ExecutorService executorService - ) { + public MySQLDatabaseProvider(@NonNull MySQLConfiguration config) { super(DEFAULT_REMOVAL_LISTENER); this.config = config; } diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseTest.java b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseTest.java new file mode 100644 index 0000000000..28dcfe0c5a --- /dev/null +++ b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseTest.java @@ -0,0 +1,184 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.mysql.impl; + +import eu.cloudnetservice.driver.document.Document; +import eu.cloudnetservice.driver.network.HostAndPort; +import eu.cloudnetservice.modules.mysql.config.DatabaseType; +import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.mysql.impl.junit.EnableServicesInject; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@EnableServicesInject +@Testcontainers(disabledWithoutDocker = true) +class JooqDatabaseTest { + + @Container + private final GenericContainer mysqlContainer = new GenericContainer<>("mariadb:latest") + .withExposedPorts(3306) + .withEnv("MYSQL_USER", "test") + .withEnv("MYSQL_PASSWORD", "test") + .withEnv("MYSQL_ROOT_PASSWORD", "test") + .withEnv("MYSQL_DATABASE", "cn_testing") + .withCommand("mariadbd", "--ssl=0"); + + private JooqProvider databaseProvider; + + @BeforeEach + void setup() { + var config = new JooqConfigurationEntry( + DatabaseType.MARIADB, + "mysql", + "cn_testing", + "test", + "test", + new HostAndPort(this.mysqlContainer.getHost(), this.mysqlContainer.getFirstMappedPort()), + null); + this.databaseProvider = new JooqProvider(config); + this.databaseProvider.init(); + } + + @Test + void testAccessCreatesDatabase() { + Assertions.assertNotNull(this.databaseProvider.database("hello_world")); + Assertions.assertNotNull(this.databaseProvider.database("hello2_world")); + + var names = this.databaseProvider.databaseNames(); + Assertions.assertTrue(names.contains("hello_world")); + Assertions.assertTrue(names.contains("hello2_world")); + } + + @Test + void testDatabaseDeletion() { + Assertions.assertNotNull(this.databaseProvider.database("hello_world")); + Assertions.assertNotNull(this.databaseProvider.database("hello2_world")); + + var names = this.databaseProvider.databaseNames(); + Assertions.assertTrue(names.contains("hello_world")); + Assertions.assertTrue(names.contains("hello2_world")); + + Assertions.assertTrue(this.databaseProvider.deleteDatabase("hello_world")); + Assertions.assertTrue(this.databaseProvider.deleteDatabase("hello2_world")); + + Assertions.assertTrue(this.databaseProvider.databaseNames().isEmpty()); + } + + @Test + void testBasicDatabaseOperations() { + var database = this.databaseProvider.database("test"); + Assertions.assertNotNull(database); + + Assertions.assertTrue(database.insert("1234", Document.newJsonDocument().append("hello", "world"))); + Assertions.assertTrue(database.insert("12234", Document.newJsonDocument().append("hello", "world2"))); + Assertions.assertTrue(database.insert("122234", Document.newJsonDocument().append("hello", "world_123"))); + + Assertions.assertTrue(database.contains("1234")); + Assertions.assertTrue(database.contains("12234")); + Assertions.assertTrue(database.contains("122234")); + + Assertions.assertEquals(3, database.documentCount()); + + var keys = database.keys(); + Assertions.assertEquals(3, keys.size()); + Assertions.assertTrue(keys.contains("1234")); + Assertions.assertTrue(keys.contains("12234")); + Assertions.assertTrue(keys.contains("122234")); + + var entry = database.get("1234"); + Assertions.assertNotNull(entry); + Assertions.assertEquals("world", entry.getString("hello")); + + var entry2 = database.get("12234"); + Assertions.assertNotNull(entry2); + Assertions.assertEquals("world2", entry2.getString("hello")); + + var entry3 = database.get("122334"); + Assertions.assertNull(entry3); + + var entry4 = database.find("hello", "world"); + Assertions.assertEquals(1, entry4.size()); + Assertions.assertEquals("world", entry4.iterator().next().getString("hello")); + + var entry5 = database.find(Map.of("hello", "world2")); + Assertions.assertEquals(1, entry5.size()); + Assertions.assertEquals("world2", entry5.iterator().next().getString("hello")); + + var entry6 = database.find("hello", "world_123"); + Assertions.assertEquals(1, entry6.size()); + Assertions.assertEquals("world_123", entry6.iterator().next().getString("hello")); + + var entries = database.entries(); + Assertions.assertEquals(3, entries.size()); + Assertions.assertEquals("world", entries.get("1234").getString("hello")); + Assertions.assertEquals("world2", entries.get("12234").getString("hello")); + + var documents = database.documents(); + Assertions.assertEquals(3, documents.size()); + + Assertions.assertTrue(database.delete("12234")); + Assertions.assertEquals(2, database.documentCount()); + + database.clear(); + Assertions.assertEquals(0, database.documentCount()); + + Assertions.assertFalse(database.delete("1234")); + } + + @Test + void testChunkedDataRead() { + var database = this.databaseProvider.database("test"); + Assertions.assertNotNull(database); + + // fill in some data + var entries = 1235; + List keys = new ArrayList<>(); + var expectedReadCounts = (int) Math.ceil(entries / 50D); + + for (var i = 0; i < entries; i++) { + var key = UUID.randomUUID().toString(); + + keys.add(key); + database.insert(key, Document.newJsonDocument().append("this_is", "a_world_test")); + } + + Assertions.assertEquals(entries, database.documentCount()); + + var index = 0; + var readsCalled = 0; + + Map currentChunk; + while ((currentChunk = database.readChunk(index, 50)) != null) { + index += 50; + readsCalled++; + + Assertions.assertFalse(currentChunk.size() > 50); + Assertions.assertTrue(keys.removeAll(currentChunk.keySet())); + } + + Assertions.assertEquals(expectedReadCounts, readsCalled); + Assertions.assertTrue(keys.isEmpty()); + } +} diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseTest.java b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseTest.java index 0cba9af7d7..68d20c0363 100644 --- a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseTest.java +++ b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseTest.java @@ -55,8 +55,7 @@ void setup() { "mysql", List.of(new MySQLConnectionEndpoint( "cn_testing", - new HostAndPort(this.mysqlContainer.getHost(), this.mysqlContainer.getFirstMappedPort())))), - null); + new HostAndPort(this.mysqlContainer.getHost(), this.mysqlContainer.getFirstMappedPort()))))); this.databaseProvider.init(); } diff --git a/node/api/src/main/java/eu/cloudnetservice/node/database/LocalDatabase.java b/node/api/src/main/java/eu/cloudnetservice/node/database/LocalDatabase.java index 43351b9d33..ccf2d8969b 100644 --- a/node/api/src/main/java/eu/cloudnetservice/node/database/LocalDatabase.java +++ b/node/api/src/main/java/eu/cloudnetservice/node/database/LocalDatabase.java @@ -25,14 +25,6 @@ public interface LocalDatabase extends Database { - /** - * Iterates over all entries in the database This option should not be used with big databases Use - * {@link #iterate(BiConsumer, int)}} instead - * - * @param consumer the consumer to pass the entries into - */ - void iterate(@NonNull BiConsumer consumer); - /** * Iterates over all entries in the database, but in chunks in the given size * diff --git a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/h2/H2Database.java b/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/h2/H2Database.java index edf662a9ae..24c97932d5 100644 --- a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/h2/H2Database.java +++ b/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/h2/H2Database.java @@ -29,7 +29,6 @@ import java.util.Objects; import java.util.Set; import java.util.WeakHashMap; -import java.util.function.BiConsumer; import lombok.NonNull; import org.jetbrains.annotations.Nullable; @@ -203,21 +202,6 @@ public boolean delete0(String key) { }, Map.of()); } - @Override - public void iterate(@NonNull BiConsumer consumer) { - this.databaseProvider.executeQuery( - String.format("SELECT * FROM `%s`;", this.name), - resultSet -> { - while (resultSet.next()) { - var key = resultSet.getString(TABLE_COLUMN_KEY); - var document = DocumentFactory.json().parse(resultSet.getString(TABLE_COLUMN_VAL)); - consumer.accept(key, document); - } - - return null; - }, null); - } - @Override public void clear() { this.databaseProvider.executeUpdate(String.format("TRUNCATE TABLE `%s`", this.name)); diff --git a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/xodus/XodusDatabase.java b/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/xodus/XodusDatabase.java index 736c397850..c65ab3b74d 100644 --- a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/xodus/XodusDatabase.java +++ b/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/xodus/XodusDatabase.java @@ -116,11 +116,6 @@ public boolean delete(@NonNull String key) { return result; } - @Override - public void iterate(@NonNull BiConsumer consumer) { - this.acceptWithCursor(consumer); - } - @Override public void clear() { this.environment.executeInExclusiveTransaction(txn -> { From eb39b2f3e096653306b19339ff9f6ae97d69d5fb Mon Sep 17 00:00:00 2001 From: 0utplay Date: Wed, 28 Jan 2026 21:35:54 +0100 Subject: [PATCH 02/13] feat: dies und das --- .../modules/mysql/config/DatabaseType.java | 2 +- .../mysql/config/JooqConfigurationEntry.java | 4 +- ...point.java => SQLModuleConfiguration.java} | 4 +- modules/database-mysql/impl/build.gradle.kts | 3 +- .../modules/mysql/impl/MySQLDatabase.java | 229 -------------- .../mysql/impl/MySQLDatabaseProvider.java | 164 ---------- .../impl/CloudNetMySQLDatabaseModule.java | 57 ++-- .../impl/DocumentConverter.java | 2 +- .../{mysql => sql}/impl/JooqDatabase.java | 27 +- .../{mysql => sql}/impl/JooqDatabaseType.java | 19 +- .../{mysql => sql}/impl/JooqProvider.java | 34 +- .../modules/sql/impl/TableCreator.java | 29 ++ .../impl/sqlite/SQLiteDatabaseProvider.java} | 29 +- .../modules/mysql/impl/JooqDatabaseTest.java | 184 ----------- .../modules/mysql/impl/MySQLDatabaseTest.java | 182 ----------- .../modules/sql/impl/MariaDBDatabaseTest.java | 54 ++++ .../modules/sql/impl/MySQLDBDatabaseTest.java | 53 ++++ .../sql/impl/PostgreSQLDatabaseTest.java | 53 ++++ .../modules/sql/impl/SQLDatabaseTest.java | 294 ++++++++++++++++++ .../modules/sql/impl/SQLiteDatabaseTest.java | 40 +++ .../impl/junit/EnableServicesInject.java | 2 +- .../junit/EnableServicesInjectExtension.java | 2 +- 22 files changed, 627 insertions(+), 840 deletions(-) rename modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/{MySQLConnectionEndpoint.java => SQLModuleConfiguration.java} (82%) delete mode 100644 modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabase.java delete mode 100644 modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseProvider.java rename modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/{mysql => sql}/impl/CloudNetMySQLDatabaseModule.java (59%) rename modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/{mysql => sql}/impl/DocumentConverter.java (96%) rename modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/{mysql => sql}/impl/JooqDatabase.java (84%) rename modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/{mysql => sql}/impl/JooqDatabaseType.java (69%) rename modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/{mysql => sql}/impl/JooqProvider.java (74%) create mode 100644 modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/TableCreator.java rename modules/database-mysql/{api/src/main/java/eu/cloudnetservice/modules/mysql/config/MySQLConfiguration.java => impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java} (50%) delete mode 100644 modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseTest.java delete mode 100644 modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseTest.java create mode 100644 modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java create mode 100644 modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java create mode 100644 modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java create mode 100644 modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLDatabaseTest.java create mode 100644 modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java rename modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/{mysql => sql}/impl/junit/EnableServicesInject.java (95%) rename modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/{mysql => sql}/impl/junit/EnableServicesInjectExtension.java (98%) diff --git a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/DatabaseType.java b/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/DatabaseType.java index 419d73311b..036758f9f6 100644 --- a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/DatabaseType.java +++ b/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/DatabaseType.java @@ -17,7 +17,7 @@ package eu.cloudnetservice.modules.mysql.config; public enum DatabaseType { - + MAGIC_MIKE, POSTGRES, MYSQL, MARIADB, diff --git a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/JooqConfigurationEntry.java b/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/JooqConfigurationEntry.java index b9870912b7..d54ad530b1 100644 --- a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/JooqConfigurationEntry.java +++ b/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/JooqConfigurationEntry.java @@ -45,11 +45,11 @@ public record JooqConfigurationEntry( var jdbcDescriptor = switch (this.databaseType) { case MYSQL -> "mysql"; case MARIADB -> "mariadb"; + case MAGIC_MIKE -> throw new IllegalArgumentException("magic mike"); case POSTGRES -> "postgresql"; - case SQLITE -> "sqlite"; + case SQLITE -> "sqlite"; }; return String.format(JDBC_URI, jdbcDescriptor, this.address.host(), this.address.port(), this.databaseName); } - } diff --git a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/MySQLConnectionEndpoint.java b/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/SQLModuleConfiguration.java similarity index 82% rename from modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/MySQLConnectionEndpoint.java rename to modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/SQLModuleConfiguration.java index ca16efa083..a308138487 100644 --- a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/MySQLConnectionEndpoint.java +++ b/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/SQLModuleConfiguration.java @@ -16,9 +16,9 @@ package eu.cloudnetservice.modules.mysql.config; -import eu.cloudnetservice.driver.network.HostAndPort; +import java.util.List; import lombok.NonNull; -public record MySQLConnectionEndpoint(@NonNull String database, @NonNull HostAndPort address) { +public record SQLModuleConfiguration(@NonNull List entries) { } diff --git a/modules/database-mysql/impl/build.gradle.kts b/modules/database-mysql/impl/build.gradle.kts index c36eb4005c..bdc64fc789 100644 --- a/modules/database-mysql/impl/build.gradle.kts +++ b/modules/database-mysql/impl/build.gradle.kts @@ -32,6 +32,7 @@ dependencies { api(projects.modules.databaseMysql.databaseMysqlApi) implementation("org.jooq:jooq:3.20.10") + implementation("org.xerial:sqlite-jdbc:3.51.1.0") implementation("org.postgresql:postgresql:42.7.9") implementation("com.mysql:mysql-connector-j:9.5.0") implementation("org.mariadb.jdbc:mariadb-java-client:3.5.7") @@ -44,7 +45,7 @@ tasks.shadowJar.configure { moduleJson { author = "CloudNetService" name = "CloudNet-Database-MySQL" - main = "eu.cloudnetservice.modules.mysql.impl.CloudNetMySQLDatabaseModule" + main = "eu.cloudnetservice.modules.sql.impl.CloudNetMySQLDatabaseModule" description = "CloudNet extension, which includes the database support for MySQL and MariaDB" minJavaVersionId = JavaVersion.VERSION_11 runtimeModule = true diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabase.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabase.java deleted file mode 100644 index 1c9cf13b74..0000000000 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabase.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2019-present CloudNetService team & contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.cloudnetservice.modules.mysql.impl; - -import eu.cloudnetservice.driver.document.Document; -import eu.cloudnetservice.driver.document.DocumentFactory; -import eu.cloudnetservice.node.impl.database.sql.SQLDatabase; -import eu.cloudnetservice.node.impl.database.sql.SQLDatabaseProvider; -import java.sql.ResultSet; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import lombok.NonNull; -import org.jetbrains.annotations.Nullable; - -public final class MySQLDatabase extends SQLDatabase { - - public MySQLDatabase(@NonNull SQLDatabaseProvider provider, @NonNull String name) { - super(provider, name); - - // create the table - provider.executeUpdate(String.format( - "CREATE TABLE IF NOT EXISTS `%s` (%s VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY, %s JSON NOT NULL);", - name, - TABLE_COLUMN_KEY, - TABLE_COLUMN_VAL)); - - // alter mysql tables - provider.executeUpdate(String.format("ALTER TABLE `%s` MODIFY `%s` VARCHAR(512), MODIFY %s JSON NOT NULL", - name, - TABLE_COLUMN_KEY, - TABLE_COLUMN_VAL)); - } - - @Override - public boolean insert(@NonNull String key, @NonNull Document document) { - var serializedDocument = this.serializeDocumentToJsonString(document); - return this.databaseProvider.executeUpdate( - String.format( - "INSERT INTO `%s` (%s, %s) VALUES (?, ?) ON DUPLICATE KEY UPDATE %s = ?;", - this.name, - TABLE_COLUMN_KEY, - TABLE_COLUMN_VAL, - TABLE_COLUMN_VAL), - key, serializedDocument, serializedDocument) > 0; - } - - @Override - public boolean contains(@NonNull String key) { - return this.databaseProvider.executeQuery( - String.format("SELECT %s FROM `%s` WHERE %s = ?;", TABLE_COLUMN_KEY, this.name, TABLE_COLUMN_KEY), - ResultSet::next, - false, - key); - } - - @Override - public boolean delete(@NonNull String key) { - return this.databaseProvider.executeUpdate( - String.format("DELETE FROM %s WHERE `%s` = ?;", this.name, TABLE_COLUMN_KEY), - key) > 0; - } - - @Override - public @Nullable Document get(@NonNull String key) { - return this.databaseProvider.executeQuery( - String.format("SELECT %s FROM `%s` WHERE %s = ?;", TABLE_COLUMN_VAL, this.name, TABLE_COLUMN_KEY), - resultSet -> { - if (resultSet.next()) { - return DocumentFactory.json().parse(resultSet.getString(TABLE_COLUMN_VAL)); - } - - return null; - }, null, key); - } - - @Override - public @NonNull Collection find(@NonNull String fieldName, @Nullable String fieldValue) { - return this.databaseProvider.executeQuery( - String.format( - "SELECT %s FROM `%s` WHERE JSON_SEARCH(%s, 'one', '%s', NULL, '$.%s') IS NOT NULL;", - TABLE_COLUMN_VAL, - this.name, - TABLE_COLUMN_VAL, - Objects.toString(fieldValue).replaceAll("([_%])", "\\\\$1"), - fieldName), - resultSet -> { - List results = new ArrayList<>(); - while (resultSet.next()) { - results.add(DocumentFactory.json().parse(resultSet.getString(TABLE_COLUMN_VAL))); - } - - return results; - }, List.of()); - } - - @Override - public @NonNull Collection find(@NonNull Map filters) { - var stringBuilder = new StringBuilder("SELECT ") - .append(TABLE_COLUMN_VAL) - .append(" FROM `") - .append(this.name) - .append('`'); - - if (!filters.isEmpty()) { - stringBuilder.append(" WHERE "); - var iterator = filters.entrySet().iterator(); - while (iterator.hasNext()) { - var entry = iterator.next(); - stringBuilder - .append("JSON_SEARCH(") - .append(TABLE_COLUMN_VAL) - .append(", 'one', '") - .append(entry.getValue().replaceAll("([_%])", "\\\\$1")) - .append("', NULL, '$.") - .append(entry.getKey()) - .append("') IS NOT NULL") - .append(iterator.hasNext() ? " AND " : ';'); - } - } - - return this.databaseProvider.executeQuery(stringBuilder.toString(), resultSet -> { - List results = new ArrayList<>(); - while (resultSet.next()) { - results.add(DocumentFactory.json().parse(resultSet.getString(TABLE_COLUMN_VAL))); - } - - return results; - }, List.of()); - } - - @Override - public @NonNull Collection keys() { - return this.databaseProvider.executeQuery(String.format("SELECT %s FROM `%s`;", TABLE_COLUMN_KEY, this.name), - resultSet -> { - List results = new ArrayList<>(); - while (resultSet.next()) { - results.add(resultSet.getString(TABLE_COLUMN_KEY)); - } - - return results; - }, Set.of()); - } - - @Override - public @NonNull Collection documents() { - return this.databaseProvider.executeQuery(String.format("SELECT %s FROM `%s`;", TABLE_COLUMN_VAL, this.name), - resultSet -> { - List results = new ArrayList<>(); - while (resultSet.next()) { - results.add(DocumentFactory.json().parse(resultSet.getString(TABLE_COLUMN_VAL))); - } - - return results; - }, Set.of()); - } - - @Override - public @NonNull Map entries() { - return this.databaseProvider.executeQuery(String.format("SELECT * FROM `%s`;", this.name), resultSet -> { - Map results = new HashMap<>(); - while (resultSet.next()) { - results.put( - resultSet.getString(TABLE_COLUMN_KEY), - DocumentFactory.json().parse(resultSet.getString(TABLE_COLUMN_VAL))); - } - - return results; - }, Map.of()); - } - - @Override - public void clear() { - this.databaseProvider.executeUpdate(String.format("TRUNCATE TABLE `%s`;", this.name)); - } - - @Override - public long documentCount() { - return this.databaseProvider.executeQuery("SELECT COUNT(*) FROM `" + this.name + "`;", resultSet -> { - if (resultSet.next()) { - return resultSet.getLong(1); - } - return -1L; - }, -1L); - } - - @Override - public boolean synced() { - return true; - } - - @Override - public @Nullable Map readChunk(long beginIndex, int chunkSize) { - return this.databaseProvider.executeQuery( - String.format("SELECT * FROM `%s` ORDER BY `%s` LIMIT ? OFFSET ?;", this.name, TABLE_COLUMN_KEY), - resultSet -> { - Map result = new HashMap<>(); - while (resultSet.next()) { - var key = resultSet.getString(TABLE_COLUMN_KEY); - var document = DocumentFactory.json().parse(resultSet.getString(TABLE_COLUMN_VAL)); - result.put(key, document); - } - - return result.isEmpty() ? null : result; - }, null, chunkSize, beginIndex); - } - - @Override - public void close() { - } -} diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseProvider.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseProvider.java deleted file mode 100644 index 4bff442687..0000000000 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseProvider.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2019-present CloudNetService team & contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.cloudnetservice.modules.mysql.impl; - -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import eu.cloudnetservice.modules.mysql.config.MySQLConfiguration; -import eu.cloudnetservice.node.database.LocalDatabase; -import eu.cloudnetservice.node.impl.database.sql.SQLDatabaseProvider; -import io.vavr.CheckedFunction1; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Set; -import lombok.NonNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; - -public final class MySQLDatabaseProvider extends SQLDatabaseProvider { - - private static final String CONNECT_URL_FORMAT = "jdbc:mysql://%s:%d/%s?serverTimezone=UTC"; - - private final MySQLConfiguration config; - private volatile HikariDataSource hikariDataSource; - - public MySQLDatabaseProvider(@NonNull MySQLConfiguration config) { - super(DEFAULT_REMOVAL_LISTENER); - this.config = config; - } - - @Override - public boolean init() { - var hikariConfig = new HikariConfig(); - var endpoint = this.config.randomEndpoint(); - - hikariConfig.setJdbcUrl(String.format( - CONNECT_URL_FORMAT, - endpoint.address().host(), endpoint.address().port(), endpoint.database())); - hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver"); - hikariConfig.setUsername(this.config.username()); - hikariConfig.setPassword(this.config.password()); - - hikariConfig.addDataSourceProperty("cachePrepStmts", "true"); - hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250"); - hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); - hikariConfig.addDataSourceProperty("useServerPrepStmts", "true"); - hikariConfig.addDataSourceProperty("useLocalSessionState", "true"); - hikariConfig.addDataSourceProperty("rewriteBatchedStatements", "true"); - hikariConfig.addDataSourceProperty("cacheResultSetMetadata", "true"); - hikariConfig.addDataSourceProperty("cacheServerConfiguration", "true"); - hikariConfig.addDataSourceProperty("elideSetAutoCommits", "true"); - hikariConfig.addDataSourceProperty("maintainTimeStats", "false"); - - hikariConfig.setMinimumIdle(2); - hikariConfig.setMaximumPoolSize(100); - hikariConfig.setConnectionTimeout(10_000); - hikariConfig.setValidationTimeout(10_000); - - this.hikariDataSource = new HikariDataSource(hikariConfig); - return true; - } - - @Override - public @NonNull LocalDatabase database(@NonNull String name) { - return this.databaseCache.get(name, _ -> new MySQLDatabase(this, name)); - } - - @Override - public boolean deleteDatabase(@NonNull String name) { - return this.executeUpdate(String.format("DROP TABLE IF EXISTS `%s`;", name)) != -1; - } - - @Override - public @NonNull Collection databaseNames() { - try (var connection = this.hikariDataSource.getConnection(); - var meta = connection.getMetaData().getTables(null, null, null, TABLE_TYPE)) { - // now we just need to extract the name from of the tables from the result set - Collection names = new ArrayList<>(); - while (meta.next()) { - names.add(meta.getString("table_name")); - } - return names; - } catch (SQLException exception) { - LOGGER.error("Exception listing tables", exception); - return Set.of(); - } - } - - @Override - public @NonNull String name() { - return this.config.databaseServiceName(); - } - - @Override - public void close() throws Exception { - super.close(); - this.hikariDataSource.close(); - } - - @Override - public @NonNull Connection connection() { - try { - return this.hikariDataSource.getConnection(); - } catch (SQLException exception) { - throw new IllegalStateException("Unable to retrieve connection from pool", exception); - } - } - - @Override - public int executeUpdate(@NonNull String query, @NonNull Object... objects) { - try (var con = this.connection(); var statement = con.prepareStatement(query)) { - // write all parameters - for (var i = 0; i < objects.length; i++) { - statement.setObject(i + 1, objects[i]); - } - - // execute the statement - return statement.executeUpdate(); - } catch (SQLException exception) { - LOGGER.error("Exception while executing database update", exception); - return -1; - } - } - - @Override - public @UnknownNullability T executeQuery( - @NonNull String query, - @NonNull CheckedFunction1 callback, - @Nullable T def, - @NonNull Object... objects - ) { - try (var con = this.connection(); var statement = con.prepareStatement(query)) { - // write all parameters - for (var i = 0; i < objects.length; i++) { - statement.setObject(i + 1, objects[i]); - } - - // execute the statement, apply to the result handler - try (var resultSet = statement.executeQuery()) { - return callback.apply(resultSet); - } - } catch (Throwable throwable) { - LOGGER.error("Exception while executing database query", throwable); - } - - return def; - } -} diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/CloudNetMySQLDatabaseModule.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetMySQLDatabaseModule.java similarity index 59% rename from modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/CloudNetMySQLDatabaseModule.java rename to modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetMySQLDatabaseModule.java index d5b7aead6e..cbceb55ee3 100644 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/CloudNetMySQLDatabaseModule.java +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetMySQLDatabaseModule.java @@ -14,31 +14,32 @@ * limitations under the License. */ -package eu.cloudnetservice.modules.mysql.impl; +package eu.cloudnetservice.modules.sql.impl; -import eu.cloudnetservice.driver.document.Document; import eu.cloudnetservice.driver.document.DocumentFactory; import eu.cloudnetservice.driver.module.ModuleLifeCycle; import eu.cloudnetservice.driver.module.ModuleTask; import eu.cloudnetservice.driver.module.driver.DriverModule; import eu.cloudnetservice.driver.network.HostAndPort; import eu.cloudnetservice.driver.registry.ServiceRegistry; -import eu.cloudnetservice.modules.mysql.config.MySQLConfiguration; -import eu.cloudnetservice.modules.mysql.config.MySQLConnectionEndpoint; +import eu.cloudnetservice.modules.mysql.config.DatabaseType; +import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.mysql.config.SQLModuleConfiguration; import eu.cloudnetservice.node.impl.database.NodeDatabaseProvider; -import io.leangen.geantyref.TypeFactory; import jakarta.inject.Singleton; import java.util.List; import lombok.NonNull; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; @Singleton public final class CloudNetMySQLDatabaseModule extends DriverModule { - private volatile MySQLConfiguration configuration; + private volatile SQLModuleConfiguration configuration; @ModuleTask(order = 127, lifecycle = ModuleLifeCycle.LOADED) public void convertConfig() { - var config = this.readConfig(DocumentFactory.json()); + /*var config = this.readConfig(DocumentFactory.json()); if (config.contains("addresses")) { // convert all entries this.writeConfig(Document.newJsonDocument().appendTree(new MySQLConfiguration( @@ -47,31 +48,45 @@ public void convertConfig() { config.getString("database"), config.readObject("addresses", TypeFactory.parameterizedClass(List.class, MySQLConnectionEndpoint.class)) ))); - } + }*/ } @ModuleTask(order = 125, lifecycle = ModuleLifeCycle.LOADED) public void registerDatabaseProvider(@NonNull ServiceRegistry serviceRegistry) { this.configuration = this.readConfig( - MySQLConfiguration.class, - () -> new MySQLConfiguration( - "root", - "123456", - "mysql", - List.of(new MySQLConnectionEndpoint("cloudnet", new HostAndPort("127.0.0.1", 3306)))), + SQLModuleConfiguration.class, + () -> new SQLModuleConfiguration(List.of(new JooqConfigurationEntry( + DatabaseType.MAGIC_MIKE, + "sql", + "cloudnet", + "cloudnet", + "password", + new HostAndPort("127.0.0.1", 3306), + null + ))), DocumentFactory.json()); - serviceRegistry.registerProvider( - NodeDatabaseProvider.class, - this.configuration.databaseServiceName(), - new MySQLDatabaseProvider(this.configuration)); + for (var entry : this.configuration.entries()) { + serviceRegistry.registerProvider( + NodeDatabaseProvider.class, + entry.databaseServiceName(), + new JooqProvider(((dslContext, name) -> { + dslContext.createTableIfNotExists(DSL.name(name)) + .column(JooqDatabase.KEY_FIELD, SQLDataType.VARCHAR(512) + .notNull() + .collation(DSL.collation("utf8mb4_bin"))) + .execute(); + }), entry)); + } } @ModuleTask(order = 127, lifecycle = ModuleLifeCycle.STOPPED) public void unregisterDatabaseProvider(@NonNull ServiceRegistry serviceRegistry) { - var service = serviceRegistry.registration(NodeDatabaseProvider.class, this.configuration.databaseServiceName()); - if (service != null) { - service.unregister(); + for (var entry : this.configuration.entries()) { + var service = serviceRegistry.registration(NodeDatabaseProvider.class, entry.databaseServiceName()); + if (service != null) { + service.unregister(); + } } } } diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/DocumentConverter.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/DocumentConverter.java similarity index 96% rename from modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/DocumentConverter.java rename to modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/DocumentConverter.java index eaf22db511..4deeb64489 100644 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/DocumentConverter.java +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/DocumentConverter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package eu.cloudnetservice.modules.mysql.impl; +package eu.cloudnetservice.modules.sql.impl; import eu.cloudnetservice.driver.document.Document; import eu.cloudnetservice.driver.document.DocumentFactory; diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabase.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabase.java similarity index 84% rename from modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabase.java rename to modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabase.java index 298c7bac09..e0721c1d5b 100644 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabase.java +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package eu.cloudnetservice.modules.mysql.impl; +package eu.cloudnetservice.modules.sql.impl; import eu.cloudnetservice.driver.document.Document; import eu.cloudnetservice.node.impl.database.AbstractDatabase; @@ -34,14 +34,13 @@ import org.jooq.Record1; import org.jooq.Table; import org.jooq.impl.DSL; -import org.jooq.impl.SQLDataType; public class JooqDatabase extends AbstractDatabase { - private static final Name KEY_FIELD_NAME = DSL.name("Name"); - private static final Field KEY_FIELD = DSL.field(KEY_FIELD_NAME, String.class); - private static final Name DOCUMENT_FIELD_NAME = DSL.name("Document"); - private static final Field DOCUMENT_FIELD = DSL + static final Name KEY_FIELD_NAME = DSL.name("Name"); + static final Field KEY_FIELD = DSL.field(KEY_FIELD_NAME, String.class); + static final Name DOCUMENT_FIELD_NAME = DSL.name("Document"); + static final Field DOCUMENT_FIELD = DSL .field(DOCUMENT_FIELD_NAME, JSONB.class) .convert(DocumentConverter.INSTANCE); @@ -49,7 +48,7 @@ public class JooqDatabase extends AbstractDatabase { protected final Table dslTable; protected final DSLContext dslContext; - protected JooqDatabase( + public JooqDatabase( @NonNull String name, @NonNull JooqProvider databaseProvider, @NonNull DSLContext dslContext @@ -59,21 +58,16 @@ protected JooqDatabase( this.dslName = DSL.name(name); this.dslTable = DSL.table(this.dslName); this.dslContext = dslContext; - - this.dslContext.createTableIfNotExists(this.dslTable) - .column(KEY_FIELD, SQLDataType.VARCHAR(512).notNull()) - .column(DOCUMENT_FIELD, SQLDataType.JSONB.notNull()) - .primaryKey(KEY_FIELD) - .execute(); } @Override public @Nullable Map readChunk(long beginIndex, int chunkSize) { - return this.dslContext.select(DSL.asterisk()) + var chunk = this.dslContext.select(KEY_FIELD, DOCUMENT_FIELD) .from(this.dslTable) .limit(chunkSize) .offset(beginIndex) .fetchMap(KEY_FIELD, DOCUMENT_FIELD); + return chunk.isEmpty() ? null : chunk; } @Override @@ -81,7 +75,8 @@ public boolean insert(@NonNull String key, @NonNull Document document) { return this.dslContext.insertInto(this.dslTable) .set(KEY_FIELD, key) .set(DOCUMENT_FIELD, document) - .onDuplicateKeyUpdate() + .onConflict(KEY_FIELD) + .doUpdate() .set(DOCUMENT_FIELD, document) .execute() > 0; } @@ -146,7 +141,7 @@ public boolean delete(@NonNull String key) { @Override public @NonNull Map entries() { - return this.dslContext.select(DSL.asterisk()).from(this.dslTable).fetchMap(KEY_FIELD, DOCUMENT_FIELD); + return this.dslContext.select(KEY_FIELD, DOCUMENT_FIELD).from(this.dslTable).fetchMap(KEY_FIELD, DOCUMENT_FIELD); } @Override diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseType.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java similarity index 69% rename from modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseType.java rename to modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java index 36e9c38e8e..bab9573efa 100644 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseType.java +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java @@ -14,24 +14,22 @@ * limitations under the License. */ -package eu.cloudnetservice.modules.mysql.impl; +package eu.cloudnetservice.modules.sql.impl; import eu.cloudnetservice.modules.mysql.config.DatabaseType; import lombok.NonNull; import org.jooq.SQLDialect; public enum JooqDatabaseType { - MYSQL("mysql", "com.mysql.cj.jdbc.Driver", SQLDialect.MYSQL), - MARIADB("mariadb", "org.mariadb.jdbc.Driver", SQLDialect.MARIADB), - POSTGRESQL("postgresql", "org.postgresql.Driver", SQLDialect.POSTGRES), - SQLITE("file", "org.sqlite.JDBC", SQLDialect.SQLITE); + MYSQL("com.mysql.cj.jdbc.Driver", SQLDialect.MYSQL), + MARIADB("org.mariadb.jdbc.Driver", SQLDialect.MARIADB), + POSTGRESQL("org.postgresql.Driver", SQLDialect.POSTGRES), + SQLITE("org.sqlite.JDBC", SQLDialect.SQLITE); - private final String jdbcDescriptor; private final String driverClassName; private final SQLDialect jooqDialect; - JooqDatabaseType(@NonNull String jdbcDescriptor, @NonNull String driverClassName, @NonNull SQLDialect jooqDialect) { - this.jdbcDescriptor = jdbcDescriptor; + JooqDatabaseType(@NonNull String driverClassName, @NonNull SQLDialect jooqDialect) { this.driverClassName = driverClassName; this.jooqDialect = jooqDialect; } @@ -42,13 +40,10 @@ public enum JooqDatabaseType { case MARIADB -> MARIADB; case POSTGRES -> POSTGRESQL; case SQLITE -> SQLITE; + case MAGIC_MIKE -> throw new IllegalArgumentException("magic mix"); }; } - public @NonNull String jdbcDescriptor() { - return this.jdbcDescriptor; - } - public @NonNull String driverClassName() { return this.driverClassName; } diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqProvider.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java similarity index 74% rename from modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqProvider.java rename to modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java index e8759566c6..4445ff6716 100644 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/mysql/impl/JooqProvider.java +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package eu.cloudnetservice.modules.mysql.impl; +package eu.cloudnetservice.modules.sql.impl; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; @@ -24,17 +24,22 @@ import java.util.Collection; import lombok.NonNull; import org.jooq.DSLContext; -import org.jooq.Named; +import org.jooq.Table; +import org.jooq.TableOptions; import org.jooq.impl.DSL; public class JooqProvider extends AbstractNodeDatabaseProvider { - private final JooqConfigurationEntry config; - private DSLContext dslContext; + protected final TableCreator tableCreator; + protected final JooqConfigurationEntry config; - protected JooqProvider(@NonNull JooqConfigurationEntry config) { + protected DSLContext dslContext; + protected HikariDataSource dataSource; + + protected JooqProvider(@NonNull TableCreator tableCreator, @NonNull JooqConfigurationEntry config) { super(DEFAULT_REMOVAL_LISTENER); + this.tableCreator = tableCreator; this.config = config; } @@ -66,12 +71,14 @@ public boolean init() { hikariConfig.setConnectionTimeout(10_000); hikariConfig.setValidationTimeout(10_000); - this.dslContext = DSL.using(new HikariDataSource(hikariConfig), databaseType.jooqDialect()); + this.dataSource = new HikariDataSource(hikariConfig); + this.dslContext = DSL.using(this.dataSource, databaseType.jooqDialect()); return true; } @Override public @NonNull LocalDatabase database(@NonNull String name) { + this.tableCreator.createTable(this.dslContext, name); return new JooqDatabase(name, this, this.dslContext); } @@ -87,11 +94,24 @@ public boolean deleteDatabase(@NonNull String name) { @Override public @NonNull Collection databaseNames() { - return this.dslContext.meta().getTables().stream().map(Named::getName).toList(); + return this.dslContext.meta() + .getTables() + .stream() + .filter(table -> table.getTableType() == TableOptions.TableType.TABLE) + .filter(table -> table.field(JooqDatabase.KEY_FIELD_NAME) != null) + .filter(table -> table.field(JooqDatabase.DOCUMENT_FIELD_NAME) != null) + .map(Table::getName) + .toList(); } @Override public @NonNull String name() { return this.config.databaseServiceName(); } + + @Override + public void close() throws Exception { + super.close(); + this.dataSource.close(); + } } diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/TableCreator.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/TableCreator.java new file mode 100644 index 0000000000..3f89baafdc --- /dev/null +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/TableCreator.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.sql.impl; + +import lombok.NonNull; +import org.jooq.DSLContext; + +@FunctionalInterface +public interface TableCreator { + + void createTable( + @NonNull DSLContext dslContext, + @NonNull String name); + +} diff --git a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/MySQLConfiguration.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java similarity index 50% rename from modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/MySQLConfiguration.java rename to modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java index 4fc824f846..c5d8b455ef 100644 --- a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/MySQLConfiguration.java +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java @@ -14,25 +14,22 @@ * limitations under the License. */ -package eu.cloudnetservice.modules.mysql.config; +package eu.cloudnetservice.modules.sql.impl.sqlite; -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; +import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.impl.JooqDatabase; +import eu.cloudnetservice.modules.sql.impl.JooqProvider; +import eu.cloudnetservice.node.database.LocalDatabase; import lombok.NonNull; -public record MySQLConfiguration( - @NonNull String username, - @NonNull String password, - @NonNull String databaseServiceName, - @NonNull List endpoints -) { +public class SQLiteDatabaseProvider extends JooqProvider { - public @NonNull MySQLConnectionEndpoint randomEndpoint() { - // check if there are any endpoints - if (this.endpoints.isEmpty()) { - throw new IllegalStateException("No mysql connection endpoints available"); - } - // return a random stream - return this.endpoints.get(ThreadLocalRandom.current().nextInt(0, this.endpoints.size())); + protected SQLiteDatabaseProvider(@NonNull JooqConfigurationEntry config) { + super((_, _) -> {}, config); + } + + @Override + public @NonNull LocalDatabase database(@NonNull String name) { + return new JooqDatabase(name, this, super.dslContext); } } diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseTest.java b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseTest.java deleted file mode 100644 index 28dcfe0c5a..0000000000 --- a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/JooqDatabaseTest.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2019-present CloudNetService team & contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.cloudnetservice.modules.mysql.impl; - -import eu.cloudnetservice.driver.document.Document; -import eu.cloudnetservice.driver.network.HostAndPort; -import eu.cloudnetservice.modules.mysql.config.DatabaseType; -import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; -import eu.cloudnetservice.modules.mysql.impl.junit.EnableServicesInject; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - -@EnableServicesInject -@Testcontainers(disabledWithoutDocker = true) -class JooqDatabaseTest { - - @Container - private final GenericContainer mysqlContainer = new GenericContainer<>("mariadb:latest") - .withExposedPorts(3306) - .withEnv("MYSQL_USER", "test") - .withEnv("MYSQL_PASSWORD", "test") - .withEnv("MYSQL_ROOT_PASSWORD", "test") - .withEnv("MYSQL_DATABASE", "cn_testing") - .withCommand("mariadbd", "--ssl=0"); - - private JooqProvider databaseProvider; - - @BeforeEach - void setup() { - var config = new JooqConfigurationEntry( - DatabaseType.MARIADB, - "mysql", - "cn_testing", - "test", - "test", - new HostAndPort(this.mysqlContainer.getHost(), this.mysqlContainer.getFirstMappedPort()), - null); - this.databaseProvider = new JooqProvider(config); - this.databaseProvider.init(); - } - - @Test - void testAccessCreatesDatabase() { - Assertions.assertNotNull(this.databaseProvider.database("hello_world")); - Assertions.assertNotNull(this.databaseProvider.database("hello2_world")); - - var names = this.databaseProvider.databaseNames(); - Assertions.assertTrue(names.contains("hello_world")); - Assertions.assertTrue(names.contains("hello2_world")); - } - - @Test - void testDatabaseDeletion() { - Assertions.assertNotNull(this.databaseProvider.database("hello_world")); - Assertions.assertNotNull(this.databaseProvider.database("hello2_world")); - - var names = this.databaseProvider.databaseNames(); - Assertions.assertTrue(names.contains("hello_world")); - Assertions.assertTrue(names.contains("hello2_world")); - - Assertions.assertTrue(this.databaseProvider.deleteDatabase("hello_world")); - Assertions.assertTrue(this.databaseProvider.deleteDatabase("hello2_world")); - - Assertions.assertTrue(this.databaseProvider.databaseNames().isEmpty()); - } - - @Test - void testBasicDatabaseOperations() { - var database = this.databaseProvider.database("test"); - Assertions.assertNotNull(database); - - Assertions.assertTrue(database.insert("1234", Document.newJsonDocument().append("hello", "world"))); - Assertions.assertTrue(database.insert("12234", Document.newJsonDocument().append("hello", "world2"))); - Assertions.assertTrue(database.insert("122234", Document.newJsonDocument().append("hello", "world_123"))); - - Assertions.assertTrue(database.contains("1234")); - Assertions.assertTrue(database.contains("12234")); - Assertions.assertTrue(database.contains("122234")); - - Assertions.assertEquals(3, database.documentCount()); - - var keys = database.keys(); - Assertions.assertEquals(3, keys.size()); - Assertions.assertTrue(keys.contains("1234")); - Assertions.assertTrue(keys.contains("12234")); - Assertions.assertTrue(keys.contains("122234")); - - var entry = database.get("1234"); - Assertions.assertNotNull(entry); - Assertions.assertEquals("world", entry.getString("hello")); - - var entry2 = database.get("12234"); - Assertions.assertNotNull(entry2); - Assertions.assertEquals("world2", entry2.getString("hello")); - - var entry3 = database.get("122334"); - Assertions.assertNull(entry3); - - var entry4 = database.find("hello", "world"); - Assertions.assertEquals(1, entry4.size()); - Assertions.assertEquals("world", entry4.iterator().next().getString("hello")); - - var entry5 = database.find(Map.of("hello", "world2")); - Assertions.assertEquals(1, entry5.size()); - Assertions.assertEquals("world2", entry5.iterator().next().getString("hello")); - - var entry6 = database.find("hello", "world_123"); - Assertions.assertEquals(1, entry6.size()); - Assertions.assertEquals("world_123", entry6.iterator().next().getString("hello")); - - var entries = database.entries(); - Assertions.assertEquals(3, entries.size()); - Assertions.assertEquals("world", entries.get("1234").getString("hello")); - Assertions.assertEquals("world2", entries.get("12234").getString("hello")); - - var documents = database.documents(); - Assertions.assertEquals(3, documents.size()); - - Assertions.assertTrue(database.delete("12234")); - Assertions.assertEquals(2, database.documentCount()); - - database.clear(); - Assertions.assertEquals(0, database.documentCount()); - - Assertions.assertFalse(database.delete("1234")); - } - - @Test - void testChunkedDataRead() { - var database = this.databaseProvider.database("test"); - Assertions.assertNotNull(database); - - // fill in some data - var entries = 1235; - List keys = new ArrayList<>(); - var expectedReadCounts = (int) Math.ceil(entries / 50D); - - for (var i = 0; i < entries; i++) { - var key = UUID.randomUUID().toString(); - - keys.add(key); - database.insert(key, Document.newJsonDocument().append("this_is", "a_world_test")); - } - - Assertions.assertEquals(entries, database.documentCount()); - - var index = 0; - var readsCalled = 0; - - Map currentChunk; - while ((currentChunk = database.readChunk(index, 50)) != null) { - index += 50; - readsCalled++; - - Assertions.assertFalse(currentChunk.size() > 50); - Assertions.assertTrue(keys.removeAll(currentChunk.keySet())); - } - - Assertions.assertEquals(expectedReadCounts, readsCalled); - Assertions.assertTrue(keys.isEmpty()); - } -} diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseTest.java b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseTest.java deleted file mode 100644 index 68d20c0363..0000000000 --- a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/MySQLDatabaseTest.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2019-present CloudNetService team & contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.cloudnetservice.modules.mysql.impl; - -import eu.cloudnetservice.driver.document.Document; -import eu.cloudnetservice.driver.network.HostAndPort; -import eu.cloudnetservice.modules.mysql.config.MySQLConfiguration; -import eu.cloudnetservice.modules.mysql.config.MySQLConnectionEndpoint; -import eu.cloudnetservice.modules.mysql.impl.junit.EnableServicesInject; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - -@EnableServicesInject -@Testcontainers(disabledWithoutDocker = true) -class MySQLDatabaseTest { - - @Container - private final GenericContainer mysqlContainer = new GenericContainer<>("mariadb:latest") - .withExposedPorts(3306) - .withEnv("MYSQL_USER", "test") - .withEnv("MYSQL_PASSWORD", "test") - .withEnv("MYSQL_ROOT_PASSWORD", "test") - .withEnv("MYSQL_DATABASE", "cn_testing") - .withCommand("mariadbd", "--ssl=0"); - - private MySQLDatabaseProvider databaseProvider; - - @BeforeEach - void setup() { - this.databaseProvider = new MySQLDatabaseProvider(new MySQLConfiguration( - "test", - "test", - "mysql", - List.of(new MySQLConnectionEndpoint( - "cn_testing", - new HostAndPort(this.mysqlContainer.getHost(), this.mysqlContainer.getFirstMappedPort()))))); - this.databaseProvider.init(); - } - - @Test - void testAccessCreatesDatabase() { - Assertions.assertNotNull(this.databaseProvider.database("hello_world")); - Assertions.assertNotNull(this.databaseProvider.database("hello2_world")); - - var names = this.databaseProvider.databaseNames(); - Assertions.assertTrue(names.contains("hello_world")); - Assertions.assertTrue(names.contains("hello2_world")); - } - - @Test - void testDatabaseDeletion() { - Assertions.assertNotNull(this.databaseProvider.database("hello_world")); - Assertions.assertNotNull(this.databaseProvider.database("hello2_world")); - - var names = this.databaseProvider.databaseNames(); - Assertions.assertTrue(names.contains("hello_world")); - Assertions.assertTrue(names.contains("hello2_world")); - - Assertions.assertTrue(this.databaseProvider.deleteDatabase("hello_world")); - Assertions.assertTrue(this.databaseProvider.deleteDatabase("hello2_world")); - - Assertions.assertTrue(this.databaseProvider.databaseNames().isEmpty()); - } - - @Test - void testBasicDatabaseOperations() { - var database = this.databaseProvider.database("test"); - Assertions.assertNotNull(database); - - Assertions.assertTrue(database.insert("1234", Document.newJsonDocument().append("hello", "world"))); - Assertions.assertTrue(database.insert("12234", Document.newJsonDocument().append("hello", "world2"))); - Assertions.assertTrue(database.insert("122234", Document.newJsonDocument().append("hello", "world_123"))); - - Assertions.assertTrue(database.contains("1234")); - Assertions.assertTrue(database.contains("12234")); - Assertions.assertTrue(database.contains("122234")); - - Assertions.assertEquals(3, database.documentCount()); - - var keys = database.keys(); - Assertions.assertEquals(3, keys.size()); - Assertions.assertTrue(keys.contains("1234")); - Assertions.assertTrue(keys.contains("12234")); - Assertions.assertTrue(keys.contains("122234")); - - var entry = database.get("1234"); - Assertions.assertNotNull(entry); - Assertions.assertEquals("world", entry.getString("hello")); - - var entry2 = database.get("12234"); - Assertions.assertNotNull(entry2); - Assertions.assertEquals("world2", entry2.getString("hello")); - - var entry3 = database.get("122334"); - Assertions.assertNull(entry3); - - var entry4 = database.find("hello", "world"); - Assertions.assertEquals(1, entry4.size()); - Assertions.assertEquals("world", entry4.iterator().next().getString("hello")); - - var entry5 = database.find(Map.of("hello", "world2")); - Assertions.assertEquals(1, entry5.size()); - Assertions.assertEquals("world2", entry5.iterator().next().getString("hello")); - - var entry6 = database.find("hello", "world_123"); - Assertions.assertEquals(1, entry6.size()); - Assertions.assertEquals("world_123", entry6.iterator().next().getString("hello")); - - var entries = database.entries(); - Assertions.assertEquals(3, entries.size()); - Assertions.assertEquals("world", entries.get("1234").getString("hello")); - Assertions.assertEquals("world2", entries.get("12234").getString("hello")); - - var documents = database.documents(); - Assertions.assertEquals(3, documents.size()); - - Assertions.assertTrue(database.delete("12234")); - Assertions.assertEquals(2, database.documentCount()); - - database.clear(); - Assertions.assertEquals(0, database.documentCount()); - - Assertions.assertFalse(database.delete("1234")); - } - - @Test - void testChunkedDataRead() { - var database = this.databaseProvider.database("test"); - Assertions.assertNotNull(database); - - // fill in some data - var entries = 1235; - List keys = new ArrayList<>(); - var expectedReadCounts = (int) Math.ceil(entries / 50D); - - for (var i = 0; i < entries; i++) { - var key = UUID.randomUUID().toString(); - - keys.add(key); - database.insert(key, Document.newJsonDocument().append("this_is", "a_world_test")); - } - - Assertions.assertEquals(entries, database.documentCount()); - - var index = 0; - var readsCalled = 0; - - Map currentChunk; - while ((currentChunk = database.readChunk(index, 50)) != null) { - index += 50; - readsCalled++; - - Assertions.assertFalse(currentChunk.size() > 50); - Assertions.assertTrue(keys.removeAll(currentChunk.keySet())); - } - - Assertions.assertEquals(expectedReadCounts, readsCalled); - Assertions.assertTrue(keys.isEmpty()); - } -} diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java new file mode 100644 index 0000000000..c38fb46d68 --- /dev/null +++ b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.sql.impl; + +import eu.cloudnetservice.driver.network.HostAndPort; +import eu.cloudnetservice.modules.mysql.config.DatabaseType; +import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.impl.junit.EnableServicesInject; +import org.junit.jupiter.api.BeforeAll; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@EnableServicesInject +@Testcontainers(disabledWithoutDocker = true) +public class MariaDBDatabaseTest extends SQLDatabaseTest { + + @Container + private static final GenericContainer mariaContainer = new GenericContainer<>("mariadb:latest") + .withExposedPorts(3306) + .withEnv("MYSQL_USER", "test") + .withEnv("MYSQL_PASSWORD", "test") + .withEnv("MYSQL_ROOT_PASSWORD", "test") + .withEnv("MYSQL_DATABASE", "cn_testing") + .withCommand("mariadbd", "--ssl=0"); + + @BeforeAll + static void setup() throws Exception { + var config = new JooqConfigurationEntry( + DatabaseType.MARIADB, + "mariadb", + "cn_testing", + "test", + "test", + new HostAndPort(mariaContainer.getHost(), mariaContainer.getFirstMappedPort()), + null); + databaseProvider = new JooqProvider(config); + databaseProvider.init(); + } +} diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java new file mode 100644 index 0000000000..22f4748222 --- /dev/null +++ b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.sql.impl; + +import eu.cloudnetservice.driver.network.HostAndPort; +import eu.cloudnetservice.modules.mysql.config.DatabaseType; +import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.impl.junit.EnableServicesInject; +import org.junit.jupiter.api.BeforeAll; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@EnableServicesInject +@Testcontainers(disabledWithoutDocker = true) +public class MySQLDBDatabaseTest extends SQLDatabaseTest { + + @Container + private static final GenericContainer mysqlContainer = new GenericContainer<>("mysql:latest") + .withExposedPorts(3306) + .withEnv("MYSQL_USER", "test") + .withEnv("MYSQL_PASSWORD", "test") + .withEnv("MYSQL_ROOT_PASSWORD", "test") + .withEnv("MYSQL_DATABASE", "cn_testing"); + + @BeforeAll + static void setup() throws Exception { + var config = new JooqConfigurationEntry( + DatabaseType.MYSQL, + "mysql", + "cn_testing", + "test", + "test", + new HostAndPort(mysqlContainer.getHost(), mysqlContainer.getFirstMappedPort()), + null); + databaseProvider = new JooqProvider(config); + databaseProvider.init(); + } +} diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java new file mode 100644 index 0000000000..7c32978be6 --- /dev/null +++ b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.sql.impl; + +import eu.cloudnetservice.driver.network.HostAndPort; +import eu.cloudnetservice.modules.mysql.config.DatabaseType; +import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.impl.junit.EnableServicesInject; +import org.junit.jupiter.api.BeforeAll; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@EnableServicesInject +@Testcontainers(disabledWithoutDocker = true) +public class PostgreSQLDatabaseTest extends SQLDatabaseTest { + + @Container + private static final GenericContainer postgresContainer = new GenericContainer<>("postgres:latest") + .withExposedPorts(5432) + .withEnv("POSTGRES_USER", "test") + .withEnv("POSTGRES_PASSWORD", "test") + .withEnv("POSTGRES_DB", "cn_testing") + .withCommand("postgres", "-c", "fsync=off"); + + @BeforeAll + static void setup() throws Exception { + var config = new JooqConfigurationEntry( + DatabaseType.POSTGRES, + "postgres", + "cn_testing", + "test", + "test", + new HostAndPort(postgresContainer.getHost(), postgresContainer.getFirstMappedPort()), + null); + databaseProvider = new JooqProvider(config); + databaseProvider.init(); + } +} diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLDatabaseTest.java b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLDatabaseTest.java new file mode 100644 index 0000000000..53db13b290 --- /dev/null +++ b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLDatabaseTest.java @@ -0,0 +1,294 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.sql.impl; + +import eu.cloudnetservice.driver.document.Document; +import eu.cloudnetservice.node.impl.database.NodeDatabaseProvider; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +abstract class SQLDatabaseTest { + + protected static NodeDatabaseProvider databaseProvider; + + @AfterEach + void cleanupDatabases() { + for (var dbName : databaseProvider.databaseNames()) { + databaseProvider.deleteDatabase(dbName); + } + } + + @Test + void testDatabaseProviderOperations() { + Assertions.assertNotNull(databaseProvider.database("hello_world")); + Assertions.assertNotNull(databaseProvider.database("hello2_world")); + + var names = databaseProvider.databaseNames(); + Assertions.assertTrue(names.contains("hello_world")); + Assertions.assertTrue(names.contains("hello2_world")); + + Assertions.assertTrue(databaseProvider.deleteDatabase("hello_world")); + Assertions.assertTrue(databaseProvider.deleteDatabase("hello2_world")); + Assertions.assertTrue(databaseProvider.databaseNames().isEmpty()); + } + + @Test + void testBasicOperations() { + var database = databaseProvider.database("test"); + Assertions.assertNotNull(database); + + Assertions.assertTrue(database.insert("1234", Document.newJsonDocument().append("hello", "world"))); + Assertions.assertTrue(database.insert("12234", Document.newJsonDocument().append("hello", "world2"))); + Assertions.assertTrue(database.insert("122234", Document.newJsonDocument().append("hello", "world_123"))); + + Assertions.assertTrue(database.contains("1234")); + Assertions.assertTrue(database.contains("12234")); + Assertions.assertTrue(database.contains("122234")); + + Assertions.assertFalse(database.contains("non_existent_key")); + Assertions.assertFalse(database.contains(UUID.randomUUID().toString())); + + Assertions.assertEquals(3, database.documentCount()); + + var entry = database.get("1234"); + Assertions.assertNotNull(entry); + Assertions.assertEquals("world", entry.getString("hello")); + + var entry2 = database.get("12234"); + Assertions.assertNotNull(entry2); + Assertions.assertEquals("world2", entry2.getString("hello")); + + Assertions.assertNull(database.get("122334")); + Assertions.assertNull(database.get("non_existent_key")); + + Assertions.assertTrue(database.insert("1234", Document.newJsonDocument().append("hello", "updated"))); + var updatedDoc = database.get("1234"); + Assertions.assertNotNull(updatedDoc); + Assertions.assertEquals("updated", updatedDoc.getString("hello")); + Assertions.assertEquals(3, database.documentCount()); + + Assertions.assertTrue(database.delete("12234")); + Assertions.assertEquals(2, database.documentCount()); + Assertions.assertFalse(database.contains("12234")); + + Assertions.assertFalse(database.delete("non_existent_key")); + Assertions.assertFalse(database.delete(UUID.randomUUID().toString())); + } + + @Test + void testCollectionOperations() { + var database = databaseProvider.database("test"); + Assertions.assertNotNull(database); + + Assertions.assertTrue(database.keys().isEmpty()); + Assertions.assertTrue(database.documents().isEmpty()); + Assertions.assertTrue(database.entries().isEmpty()); + Assertions.assertEquals(0, database.documentCount()); + + database.insert("key1", Document.newJsonDocument().append("data", "value1")); + database.insert("key2", Document.newJsonDocument().append("data", "value2")); + database.insert("key3", Document.newJsonDocument().append("data", "value3")); + + var keys = database.keys(); + Assertions.assertEquals(3, keys.size()); + Assertions.assertTrue(keys.contains("key1")); + Assertions.assertTrue(keys.contains("key2")); + Assertions.assertTrue(keys.contains("key3")); + + var documents = database.documents(); + Assertions.assertEquals(3, documents.size()); + + var entries = database.entries(); + Assertions.assertEquals(3, entries.size()); + Assertions.assertNotNull(entries.get("key1")); + Assertions.assertNotNull(entries.get("key2")); + Assertions.assertNotNull(entries.get("key3")); + Assertions.assertEquals("value1", entries.get("key1").getString("data")); + Assertions.assertEquals("value2", entries.get("key2").getString("data")); + Assertions.assertEquals("value3", entries.get("key3").getString("data")); + } + + @Test + void testFindOperations() { + var database = databaseProvider.database("test"); + Assertions.assertNotNull(database); + + database.insert("key1", Document.newJsonDocument() + .append("name", "Alice") + .append("age", "30") + .append("city", "Berlin")); + database.insert("key2", Document.newJsonDocument() + .append("name", "Bob") + .append("age", "30") + .append("city", "Munich")); + database.insert("key3", Document.newJsonDocument() + .append("name", "Charlie") + .append("age", "25") + .append("city", "Berlin")); + + var findByName = database.find("name", "Alice"); + Assertions.assertEquals(1, findByName.size()); + Assertions.assertEquals("Alice", findByName.iterator().next().getString("name")); + + var findByAge = database.find(Map.of("age", "30")); + Assertions.assertEquals(2, findByAge.size()); + + var findByCity = database.find(Map.of("city", "Berlin")); + Assertions.assertEquals(2, findByCity.size()); + + Map multiFilters = new HashMap<>(); + multiFilters.put("age", "30"); + multiFilters.put("city", "Berlin"); + var findMulti = database.find(multiFilters); + Assertions.assertEquals(1, findMulti.size()); + Assertions.assertEquals("Alice", findMulti.iterator().next().getString("name")); + + var noMatches = database.find("name", "NonExistent"); + Assertions.assertNotNull(noMatches); + Assertions.assertTrue(noMatches.isEmpty()); + + var noMatchesMap = database.find(Map.of("name", "NonExistent")); + Assertions.assertNotNull(noMatchesMap); + Assertions.assertTrue(noMatchesMap.isEmpty()); + + var nullResults = database.find("name", null); + Assertions.assertNotNull(nullResults); + } + + @Test + void testClearOperations() { + var database = databaseProvider.database("test"); + Assertions.assertNotNull(database); + + Assertions.assertDoesNotThrow(database::clear); + Assertions.assertEquals(0, database.documentCount()); + + database.insert("key1", Document.newJsonDocument().append("data", "1")); + database.insert("key2", Document.newJsonDocument().append("data", "2")); + database.insert("key3", Document.newJsonDocument().append("data", "3")); + Assertions.assertEquals(3, database.documentCount()); + + database.clear(); + Assertions.assertEquals(0, database.documentCount()); + Assertions.assertTrue(database.keys().isEmpty()); + Assertions.assertTrue(database.documents().isEmpty()); + Assertions.assertTrue(database.entries().isEmpty()); + Assertions.assertFalse(database.contains("key1")); + Assertions.assertFalse(database.contains("key2")); + Assertions.assertFalse(database.contains("key3")); + + Assertions.assertFalse(database.delete("key1")); + } + + @Test + void testChunkedDataRead() { + var database = databaseProvider.database("test"); + Assertions.assertNotNull(database); + + var entries = 1235; + List keys = new ArrayList<>(); + var expectedReadCounts = (int) Math.ceil(entries / 50D); + + for (var i = 0; i < entries; i++) { + var key = UUID.randomUUID().toString(); + keys.add(key); + database.insert(key, Document.newJsonDocument().append("this_is", "a_world_test")); + } + + Assertions.assertEquals(entries, database.documentCount()); + + var index = 0; + var readsCalled = 0; + + Map currentChunk; + while ((currentChunk = database.readChunk(index, 50)) != null) { + index += 50; + readsCalled++; + + Assertions.assertFalse(currentChunk.size() > 50); + Assertions.assertTrue(keys.removeAll(currentChunk.keySet())); + } + + Assertions.assertEquals(expectedReadCounts, readsCalled); + Assertions.assertTrue(keys.isEmpty()); + } + + @Test + void testSpecialKeysAndValues() { + var database = databaseProvider.database("test"); + Assertions.assertNotNull(database); + + database.insert("Key", Document.newJsonDocument().append("value", "uppercase")); + database.insert("key", Document.newJsonDocument().append("value", "lowercase")); + database.insert("KEY", Document.newJsonDocument().append("value", "alluppercase")); + + Assertions.assertEquals(3, database.documentCount()); + Assertions.assertTrue(database.contains("Key")); + Assertions.assertTrue(database.contains("key")); + Assertions.assertTrue(database.contains("KEY")); + + var upperCaseKey = database.get("Key"); + var lowerCaseKey = database.get("key"); + var fullUpperCaseKey = database.get("KEY"); + + Assertions.assertNotNull(upperCaseKey); + Assertions.assertNotNull(lowerCaseKey); + Assertions.assertNotNull(fullUpperCaseKey); + Assertions.assertEquals("uppercase", upperCaseKey.getString("value")); + Assertions.assertEquals("lowercase", lowerCaseKey.getString("value")); + Assertions.assertEquals("alluppercase", fullUpperCaseKey.getString("value")); + + database.clear(); + + var specialKeys = List.of( + "key-with-dashes", + "key.with.dots", + "key_with_underscores", + "key:with:colons", + "key/with/slashes" + ); + + for (var key : specialKeys) { + Assertions.assertTrue(database.insert(key, Document.newJsonDocument().append("key", key))); + } + + Assertions.assertEquals(specialKeys.size(), database.documentCount()); + + for (var key : specialKeys) { + Assertions.assertTrue(database.contains(key)); + var doc = database.get(key); + Assertions.assertNotNull(doc); + Assertions.assertEquals(key, doc.getString("key")); + } + + database.clear(); + + Assertions.assertTrue(database.insert("empty_value_key", Document.newJsonDocument().append("empty", ""))); + var emptyValueDoc = database.get("empty_value_key"); + Assertions.assertNotNull(emptyValueDoc); + Assertions.assertEquals("", emptyValueDoc.getString("empty")); + + var emptyResults = database.find("empty", ""); + Assertions.assertEquals(1, emptyResults.size()); + } +} diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java new file mode 100644 index 0000000000..aeeafc97a0 --- /dev/null +++ b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.sql.impl; + +import eu.cloudnetservice.modules.mysql.config.DatabaseType; +import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.impl.junit.EnableServicesInject; +import org.junit.jupiter.api.BeforeAll; + +@EnableServicesInject +public class SQLiteDatabaseTest extends SQLDatabaseTest { + + @BeforeAll + static void setup() throws Exception { + var config = new JooqConfigurationEntry( + DatabaseType.SQLITE, + "sqlite", + "cn_testing", + null, + null, + null, + "jdbc:sqlite::memory:"); + databaseProvider = new JooqProvider(config); + databaseProvider.init(); + } +} diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/junit/EnableServicesInject.java b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInject.java similarity index 95% rename from modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/junit/EnableServicesInject.java rename to modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInject.java index 283cb3a857..3cab548a75 100644 --- a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/junit/EnableServicesInject.java +++ b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInject.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package eu.cloudnetservice.modules.mysql.impl.junit; +package eu.cloudnetservice.modules.sql.impl.junit; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/junit/EnableServicesInjectExtension.java b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInjectExtension.java similarity index 98% rename from modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/junit/EnableServicesInjectExtension.java rename to modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInjectExtension.java index 62b605c5a3..ad1c28c57d 100644 --- a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/mysql/impl/junit/EnableServicesInjectExtension.java +++ b/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInjectExtension.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package eu.cloudnetservice.modules.mysql.impl.junit; +package eu.cloudnetservice.modules.sql.impl.junit; import eu.cloudnetservice.driver.DriverEnvironment; import eu.cloudnetservice.driver.impl.registry.DefaultServiceRegistry; From 6f041b7eb7715d78e5e9d6d5645001a31ee9fe67 Mon Sep 17 00:00:00 2001 From: 0utplay Date: Wed, 28 Jan 2026 21:41:18 +0100 Subject: [PATCH 03/13] feat: dies und das --- .../modules/sql/impl/CloudNetMySQLDatabaseModule.java | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetMySQLDatabaseModule.java b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetMySQLDatabaseModule.java index cbceb55ee3..04240e937d 100644 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetMySQLDatabaseModule.java +++ b/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetMySQLDatabaseModule.java @@ -75,6 +75,7 @@ public void registerDatabaseProvider(@NonNull ServiceRegistry serviceRegistry) { .column(JooqDatabase.KEY_FIELD, SQLDataType.VARCHAR(512) .notNull() .collation(DSL.collation("utf8mb4_bin"))) + .column(JooqDatabase.DOCUMENT_FIELD, SQLDataType.JSONB.notNull()) .execute(); }), entry)); } From c1dd72dc0faf110fcf50221d47baf7f0d79a5491 Mon Sep 17 00:00:00 2001 From: 0utplay Date: Thu, 29 Jan 2026 15:47:55 +0100 Subject: [PATCH 04/13] feat: dies und das --- .../cloudnet/gradle/util/Files.kt | 4 +- gradle/libs.versions.toml | 10 +++- .../api/build.gradle.kts | 0 .../modules/sql}/config/DatabaseType.java | 2 +- .../sql}/config/JooqConfigurationEntry.java | 2 +- .../sql}/config/SQLModuleConfiguration.java | 2 +- .../impl/build.gradle.kts | 18 ++---- .../sql/impl/CloudNetSQLDatabaseModule.java} | 59 +++++++++++++++---- .../modules/sql/impl/DocumentConverter.java | 0 .../modules/sql/impl/JooqDatabase.java | 0 .../modules/sql/impl/JooqDatabaseType.java | 2 +- .../modules/sql/impl/JooqProvider.java | 2 +- .../modules/sql/impl/TableCreator.java | 0 .../impl/sqlite/SQLiteDatabaseProvider.java | 2 +- .../modules/sql/impl/MariaDBDatabaseTest.java | 4 +- .../modules/sql/impl/MySQLDBDatabaseTest.java | 4 +- .../sql/impl/PostgreSQLDatabaseTest.java | 4 +- .../modules/sql/impl/SQLDatabaseTest.java | 0 .../modules/sql/impl/SQLiteDatabaseTest.java | 4 +- .../sql/impl/junit/EnableServicesInject.java | 0 .../junit/EnableServicesInjectExtension.java | 0 .../impl/src/test/resources/logback-test.xml | 0 settings.gradle.kts | 4 +- 23 files changed, 79 insertions(+), 44 deletions(-) rename modules/{database-mysql => database-sql}/api/build.gradle.kts (100%) rename modules/{database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql => database-sql/api/src/main/java/eu/cloudnetservice/modules/sql}/config/DatabaseType.java (93%) rename modules/{database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql => database-sql/api/src/main/java/eu/cloudnetservice/modules/sql}/config/JooqConfigurationEntry.java (97%) rename modules/{database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql => database-sql/api/src/main/java/eu/cloudnetservice/modules/sql}/config/SQLModuleConfiguration.java (93%) rename modules/{database-mysql => database-sql}/impl/build.gradle.kts (67%) rename modules/{database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetMySQLDatabaseModule.java => database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetSQLDatabaseModule.java} (59%) rename modules/{database-mysql => database-sql}/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/DocumentConverter.java (100%) rename modules/{database-mysql => database-sql}/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabase.java (100%) rename modules/{database-mysql => database-sql}/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java (96%) rename modules/{database-mysql => database-sql}/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java (98%) rename modules/{database-mysql => database-sql}/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/TableCreator.java (100%) rename modules/{database-mysql => database-sql}/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java (94%) rename modules/{database-mysql => database-sql}/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java (93%) rename modules/{database-mysql => database-sql}/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java (93%) rename modules/{database-mysql => database-sql}/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java (93%) rename modules/{database-mysql => database-sql}/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLDatabaseTest.java (100%) rename modules/{database-mysql => database-sql}/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java (89%) rename modules/{database-mysql => database-sql}/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInject.java (100%) rename modules/{database-mysql => database-sql}/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInjectExtension.java (100%) rename modules/{database-mysql => database-sql}/impl/src/test/resources/logback-test.xml (100%) diff --git a/build-extensions/src/main/kotlin/eu/cloudnetservice/cloudnet/gradle/util/Files.kt b/build-extensions/src/main/kotlin/eu/cloudnetservice/cloudnet/gradle/util/Files.kt index 85f8084a58..0a87e41a33 100644 --- a/build-extensions/src/main/kotlin/eu/cloudnetservice/cloudnet/gradle/util/Files.kt +++ b/build-extensions/src/main/kotlin/eu/cloudnetservice/cloudnet/gradle/util/Files.kt @@ -19,7 +19,6 @@ package eu.cloudnetservice.cloudnet.gradle.util object Files { const val driver = "driver.jar" - const val common = "common.jar" const val wrapper = "wrapper.jar" const val launcher = "launcher.jar" const val launcherPatcher = "launcher-patcher.jar" @@ -33,10 +32,9 @@ object Files { const val cloudflare = "cloudnet-cloudflare.jar" const val dockerizedServices = "cloudnet-dockerized-services.jar" const val databaseMongo = "cloudnet-database-mongodb.jar" - const val databaseMysql = "cloudnet-database-mysql.jar" + const val databaseSql = "cloudnet-database-sql.jar" const val labymod = "cloudnet-labymod.jar" const val npcs = "cloudnet-npcs.jar" - const val rest = "cloudnet-rest.jar" const val signs = "cloudnet-signs.jar" const val smart = "cloudnet-smart.jar" const val syncproxy = "cloudnet-syncproxy.jar" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ebc83695f9..be25437a6c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,11 +29,15 @@ stringSimilarity = "2.0.0" cloudConfirmation = "1.0.0-rc.1" # databases +jooq = "3.20.10" h2 = "1.4.197" # do not update, leads to database incompatibility xodus = "2.0.1" mongodb = "5.6.2" hikariCp = "7.0.2" mysqlConnector = "9.5.0" +sqliteConnector = "3.51.1.0" +postgresqlConnector = "42.7.9" +mariadbConnector = "3.5.7" # general oshi = "6.9.2" @@ -158,11 +162,15 @@ aerogelAuto = { group = "dev.derklaro.aerogel", name = "aerogel-auto", version.r aerogelScopedValues = { group = "dev.derklaro.aerogel", name = "aerogel-scoped-value-context-scope", version.ref = "aerogel" } # databases +jooq = { group = "org.jooq", name = "jooq", version.ref = "jooq" } h2 = { group = "com.h2database", name = "h2", version.ref = "h2" } hikariCp = { group = "com.zaxxer", name = "HikariCP", version.ref = "hikariCp" } mongodb = { group = "org.mongodb", name = "mongodb-driver-sync", version.ref = "mongodb" } xodus = { group = "org.jetbrains.xodus", name = "xodus-environment", version.ref = "xodus" } mysqlConnector = { group = "com.mysql", name = "mysql-connector-j", version.ref = "mysqlConnector" } +sqliteConnector = { group = "org.xerial", name = "sqlite-jdbc", version.ref = "sqliteConnector" } +postgresqlConnector = { group = "org.postgresql", name = "postgresql", version.ref = "postgresqlConnector" } +mariadbConnector = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version.ref = "mariadbConnector" } # platform api nukkitX = { group = "cn.nukkit", name = "nukkit", version.ref = "nukkitX" } @@ -203,7 +211,7 @@ aerogel = ["aerogel", "aerogelScopedValues", "aerogelAuto"] aerogelApi = ["aerogel", "aerogelAuto"] npcLib = ["npcLib", "npcLibLabymod"] unirest = ["unirest", "unirestGson"] -mysql = ["mysqlConnector", "hikariCp"] +sql = ["mysqlConnector", "hikariCp", "postgresqlConnector", "sqliteConnector", "mariadbConnector", "jooq"] jline = ["jlineReader", "jlineTerminal"] cloud = ["cloudCore", "cloudAnnotations", "cloudConfirmationProcessor"] cloudApi = ["cloudCoreApi", "cloudAnnotationsApi", "cloudConfirmationProcessor"] diff --git a/modules/database-mysql/api/build.gradle.kts b/modules/database-sql/api/build.gradle.kts similarity index 100% rename from modules/database-mysql/api/build.gradle.kts rename to modules/database-sql/api/build.gradle.kts diff --git a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/DatabaseType.java b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/DatabaseType.java similarity index 93% rename from modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/DatabaseType.java rename to modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/DatabaseType.java index 036758f9f6..fea9ad711e 100644 --- a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/DatabaseType.java +++ b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/DatabaseType.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package eu.cloudnetservice.modules.mysql.config; +package eu.cloudnetservice.modules.sql.config; public enum DatabaseType { MAGIC_MIKE, diff --git a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/JooqConfigurationEntry.java b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/JooqConfigurationEntry.java similarity index 97% rename from modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/JooqConfigurationEntry.java rename to modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/JooqConfigurationEntry.java index d54ad530b1..23ff4c2630 100644 --- a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/JooqConfigurationEntry.java +++ b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/JooqConfigurationEntry.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package eu.cloudnetservice.modules.mysql.config; +package eu.cloudnetservice.modules.sql.config; import eu.cloudnetservice.driver.network.HostAndPort; import java.util.Objects; diff --git a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/SQLModuleConfiguration.java b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLModuleConfiguration.java similarity index 93% rename from modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/SQLModuleConfiguration.java rename to modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLModuleConfiguration.java index a308138487..daa58eeaac 100644 --- a/modules/database-mysql/api/src/main/java/eu/cloudnetservice/modules/mysql/config/SQLModuleConfiguration.java +++ b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLModuleConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package eu.cloudnetservice.modules.mysql.config; +package eu.cloudnetservice.modules.sql.config; import java.util.List; import lombok.NonNull; diff --git a/modules/database-mysql/impl/build.gradle.kts b/modules/database-sql/impl/build.gradle.kts similarity index 67% rename from modules/database-mysql/impl/build.gradle.kts rename to modules/database-sql/impl/build.gradle.kts index bdc64fc789..3f1fd59f86 100644 --- a/modules/database-mysql/impl/build.gradle.kts +++ b/modules/database-sql/impl/build.gradle.kts @@ -23,30 +23,24 @@ plugins { } dependencies { - moduleLibrary(libs.bundles.mysql) { + moduleLibrary(libs.bundles.sql) { exclude("com.google.protobuf") } compileOnly(libs.caffeine) compileOnlyApi(projects.node.nodeImpl) - api(projects.modules.databaseMysql.databaseMysqlApi) - - implementation("org.jooq:jooq:3.20.10") - implementation("org.xerial:sqlite-jdbc:3.51.1.0") - implementation("org.postgresql:postgresql:42.7.9") - implementation("com.mysql:mysql-connector-j:9.5.0") - implementation("org.mariadb.jdbc:mariadb-java-client:3.5.7") + api(projects.modules.databaseSql.databaseSqlApi) } tasks.shadowJar.configure { - archiveFileName = Files.databaseMysql + archiveFileName = Files.databaseSql } moduleJson { author = "CloudNetService" - name = "CloudNet-Database-MySQL" - main = "eu.cloudnetservice.modules.sql.impl.CloudNetMySQLDatabaseModule" - description = "CloudNet extension, which includes the database support for MySQL and MariaDB" + name = "CloudNet-Database-SQL" + main = "eu.cloudnetservice.modules.sql.impl.CloudNetSQLModule" + description = "CloudNet extension, which includes the database support for MySQL, MariaDB, PostgreSQL and SQLite." minJavaVersionId = JavaVersion.VERSION_11 runtimeModule = true storesSensitiveData = true diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetMySQLDatabaseModule.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetSQLDatabaseModule.java similarity index 59% rename from modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetMySQLDatabaseModule.java rename to modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetSQLDatabaseModule.java index 04240e937d..76930c4f99 100644 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetMySQLDatabaseModule.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetSQLDatabaseModule.java @@ -16,39 +16,70 @@ package eu.cloudnetservice.modules.sql.impl; +import eu.cloudnetservice.driver.document.Document; import eu.cloudnetservice.driver.document.DocumentFactory; import eu.cloudnetservice.driver.module.ModuleLifeCycle; import eu.cloudnetservice.driver.module.ModuleTask; import eu.cloudnetservice.driver.module.driver.DriverModule; import eu.cloudnetservice.driver.network.HostAndPort; import eu.cloudnetservice.driver.registry.ServiceRegistry; -import eu.cloudnetservice.modules.mysql.config.DatabaseType; -import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; -import eu.cloudnetservice.modules.mysql.config.SQLModuleConfiguration; +import eu.cloudnetservice.modules.sql.config.DatabaseType; +import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.config.SQLModuleConfiguration; import eu.cloudnetservice.node.impl.database.NodeDatabaseProvider; +import io.leangen.geantyref.TypeFactory; import jakarta.inject.Singleton; import java.util.List; import lombok.NonNull; +import org.jetbrains.annotations.Nullable; import org.jooq.impl.DSL; import org.jooq.impl.SQLDataType; @Singleton -public final class CloudNetMySQLDatabaseModule extends DriverModule { +public final class CloudNetSQLDatabaseModule extends DriverModule { private volatile SQLModuleConfiguration configuration; @ModuleTask(order = 127, lifecycle = ModuleLifeCycle.LOADED) public void convertConfig() { - /*var config = this.readConfig(DocumentFactory.json()); + var config = this.readConfig(DocumentFactory.json()); + if (!config.contains("overrideConnectionUri")) { + return; + } + + var serviceName = config.getString("databaseServiceName"); + var username = config.getString("username"); + var password = config.getString("password"); if (config.contains("addresses")) { - // convert all entries - this.writeConfig(Document.newJsonDocument().appendTree(new MySQLConfiguration( - config.getString("username"), - config.getString("password"), + List addresses = config.readObject( + "addresses", + TypeFactory.parameterizedClass(List.class, LegacyConnectionEndpoint.class)); + var convertedConfig = new JooqConfigurationEntry( + DatabaseType.MYSQL, + serviceName, config.getString("database"), - config.readObject("addresses", TypeFactory.parameterizedClass(List.class, MySQLConnectionEndpoint.class)) - ))); - }*/ + username, + password, + addresses.isEmpty() ? new HostAndPort("127.0.0.1", 3306) : addresses.getFirst().address(), + null); + this.writeConfig(Document.newJsonDocument().appendTree(convertedConfig)); + } else if (config.contains("endpoints")) { + List endpoints = config.readObject( + "endpoints", + TypeFactory.parameterizedClass(List.class, LegacyConnectionEndpoint.class)); + var endpoint = endpoints.isEmpty() + ? new LegacyConnectionEndpoint("cloudnet", new HostAndPort("127.0.0.1", 3306)) + : endpoints.getFirst(); + var convertedConfig = new JooqConfigurationEntry( + DatabaseType.MYSQL, + serviceName, + endpoint.database(), + username, + password, + endpoint.address(), + null); + this.writeConfig(Document.newJsonDocument().appendTree(convertedConfig)); + } } @ModuleTask(order = 125, lifecycle = ModuleLifeCycle.LOADED) @@ -90,4 +121,8 @@ public void unregisterDatabaseProvider(@NonNull ServiceRegistry serviceRegistry) } } } + + @Deprecated + record LegacyConnectionEndpoint(@Nullable String database, @NonNull HostAndPort address) { + } } diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/DocumentConverter.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/DocumentConverter.java similarity index 100% rename from modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/DocumentConverter.java rename to modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/DocumentConverter.java diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabase.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabase.java similarity index 100% rename from modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabase.java rename to modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabase.java diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java similarity index 96% rename from modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java rename to modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java index bab9573efa..a907ef8a72 100644 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java @@ -16,7 +16,7 @@ package eu.cloudnetservice.modules.sql.impl; -import eu.cloudnetservice.modules.mysql.config.DatabaseType; +import eu.cloudnetservice.modules.sql.config.DatabaseType; import lombok.NonNull; import org.jooq.SQLDialect; diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java similarity index 98% rename from modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java rename to modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java index 4445ff6716..239b089d71 100644 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java @@ -18,7 +18,7 @@ import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; -import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; import eu.cloudnetservice.node.database.LocalDatabase; import eu.cloudnetservice.node.impl.database.AbstractNodeDatabaseProvider; import java.util.Collection; diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/TableCreator.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/TableCreator.java similarity index 100% rename from modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/TableCreator.java rename to modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/TableCreator.java diff --git a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java similarity index 94% rename from modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java rename to modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java index c5d8b455ef..b97d386be8 100644 --- a/modules/database-mysql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java @@ -16,7 +16,7 @@ package eu.cloudnetservice.modules.sql.impl.sqlite; -import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; import eu.cloudnetservice.modules.sql.impl.JooqDatabase; import eu.cloudnetservice.modules.sql.impl.JooqProvider; import eu.cloudnetservice.node.database.LocalDatabase; diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java similarity index 93% rename from modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java rename to modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java index c38fb46d68..b5978aae71 100644 --- a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java @@ -17,8 +17,8 @@ package eu.cloudnetservice.modules.sql.impl; import eu.cloudnetservice.driver.network.HostAndPort; -import eu.cloudnetservice.modules.mysql.config.DatabaseType; -import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.config.DatabaseType; +import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; import eu.cloudnetservice.modules.sql.impl.junit.EnableServicesInject; import org.junit.jupiter.api.BeforeAll; import org.testcontainers.containers.GenericContainer; diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java similarity index 93% rename from modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java rename to modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java index 22f4748222..b8fb02d51c 100644 --- a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java @@ -17,8 +17,8 @@ package eu.cloudnetservice.modules.sql.impl; import eu.cloudnetservice.driver.network.HostAndPort; -import eu.cloudnetservice.modules.mysql.config.DatabaseType; -import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.config.DatabaseType; +import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; import eu.cloudnetservice.modules.sql.impl.junit.EnableServicesInject; import org.junit.jupiter.api.BeforeAll; import org.testcontainers.containers.GenericContainer; diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java similarity index 93% rename from modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java rename to modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java index 7c32978be6..d498bde641 100644 --- a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java @@ -17,8 +17,8 @@ package eu.cloudnetservice.modules.sql.impl; import eu.cloudnetservice.driver.network.HostAndPort; -import eu.cloudnetservice.modules.mysql.config.DatabaseType; -import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.config.DatabaseType; +import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; import eu.cloudnetservice.modules.sql.impl.junit.EnableServicesInject; import org.junit.jupiter.api.BeforeAll; import org.testcontainers.containers.GenericContainer; diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLDatabaseTest.java similarity index 100% rename from modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLDatabaseTest.java rename to modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLDatabaseTest.java diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java similarity index 89% rename from modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java rename to modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java index aeeafc97a0..0da298ac6c 100644 --- a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java @@ -16,8 +16,8 @@ package eu.cloudnetservice.modules.sql.impl; -import eu.cloudnetservice.modules.mysql.config.DatabaseType; -import eu.cloudnetservice.modules.mysql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.config.DatabaseType; +import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; import eu.cloudnetservice.modules.sql.impl.junit.EnableServicesInject; import org.junit.jupiter.api.BeforeAll; diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInject.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInject.java similarity index 100% rename from modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInject.java rename to modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInject.java diff --git a/modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInjectExtension.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInjectExtension.java similarity index 100% rename from modules/database-mysql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInjectExtension.java rename to modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/junit/EnableServicesInjectExtension.java diff --git a/modules/database-mysql/impl/src/test/resources/logback-test.xml b/modules/database-sql/impl/src/test/resources/logback-test.xml similarity index 100% rename from modules/database-mysql/impl/src/test/resources/logback-test.xml rename to modules/database-sql/impl/src/test/resources/logback-test.xml diff --git a/settings.gradle.kts b/settings.gradle.kts index 2a3297d00d..c6590bdc93 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -86,8 +86,8 @@ registerSubProjects( subProjects = arrayOf("api", "impl"), ) registerSubProjects( - root = "modules:database-mysql", - prefix = "database-mysql", + root = "modules:database-sql", + prefix = "database-sql", subProjects = arrayOf("api", "impl"), ) registerSubProjects( From 3e11a9904eb53a767b4ebe5ee9f941788a783f2a Mon Sep 17 00:00:00 2001 From: 0utplay Date: Thu, 29 Jan 2026 16:10:02 +0100 Subject: [PATCH 05/13] feat: dies und das --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index be25437a6c..a7105419f6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,7 +34,7 @@ h2 = "1.4.197" # do not update, leads to database incompatibility xodus = "2.0.1" mongodb = "5.6.2" hikariCp = "7.0.2" -mysqlConnector = "9.5.0" +mysqlConnector = "9.6.0" sqliteConnector = "3.51.1.0" postgresqlConnector = "42.7.9" mariadbConnector = "3.5.7" From 8e559d0325a60c0260cadb418fe5dab1a99d1af9 Mon Sep 17 00:00:00 2001 From: 0utplay Date: Thu, 29 Jan 2026 19:41:05 +0100 Subject: [PATCH 06/13] chore: unify old database classes --- .../driver/database/Database.java | 1 + .../driver/database/DatabaseProvider.java | 9 +++ .../mongodb/impl/MongoDBDatabaseProvider.java | 5 ++ .../modules/sql/impl/JooqDatabase.java | 2 +- .../modules/sql/impl/JooqDatabaseType.java | 3 +- .../modules/sql/impl/JooqProvider.java | 7 ++- .../impl/sqlite/SQLiteDatabaseProvider.java | 6 +- .../node/impl/database/h2/H2Database.java | 17 +++-- .../impl/database/h2/H2DatabaseProvider.java | 36 +++++++---- .../node/impl/database/sql/SQLDatabase.java | 36 ----------- .../database/sql/SQLDatabaseProvider.java | 63 ------------------- .../database/xodus/XodusDatabaseProvider.java | 5 ++ 12 files changed, 68 insertions(+), 122 deletions(-) delete mode 100644 node/impl/src/main/java/eu/cloudnetservice/node/impl/database/sql/SQLDatabase.java delete mode 100644 node/impl/src/main/java/eu/cloudnetservice/node/impl/database/sql/SQLDatabaseProvider.java diff --git a/driver/api/src/main/java/eu/cloudnetservice/driver/database/Database.java b/driver/api/src/main/java/eu/cloudnetservice/driver/database/Database.java index 5fe4b100ce..4023a42b13 100644 --- a/driver/api/src/main/java/eu/cloudnetservice/driver/database/Database.java +++ b/driver/api/src/main/java/eu/cloudnetservice/driver/database/Database.java @@ -306,6 +306,7 @@ public interface Database extends Named, AutoCloseable { @NonNull CompletableFuture clearAsync(); + /** /** * Get the amount of key-value pairs currently stored in the database. *

diff --git a/driver/api/src/main/java/eu/cloudnetservice/driver/database/DatabaseProvider.java b/driver/api/src/main/java/eu/cloudnetservice/driver/database/DatabaseProvider.java index c3c3d50582..caa43bc067 100644 --- a/driver/api/src/main/java/eu/cloudnetservice/driver/database/DatabaseProvider.java +++ b/driver/api/src/main/java/eu/cloudnetservice/driver/database/DatabaseProvider.java @@ -45,6 +45,15 @@ */ public interface DatabaseProvider { + /** + * Gets if this database provider produces databases that are synced to the cluster. This means that every change made + * to the database will be directly visible to all components in the cluster rather than requiring a special sync. + * Normally synced databases are databases which are running as an external process, like MySQL or MongoDB. + * + * @return true if all modify operations are directly visible to all components in a cluster, false otherwise. + */ + boolean synced(); + /** * Retrieves or creates non-blocking a facade for a database to write and read data to. The name of the database * should be unique for later identification. diff --git a/modules/database-mongodb/impl/src/main/java/eu/cloudnetservice/modules/mongodb/impl/MongoDBDatabaseProvider.java b/modules/database-mongodb/impl/src/main/java/eu/cloudnetservice/modules/mongodb/impl/MongoDBDatabaseProvider.java index 2a081da663..79db7d1f57 100644 --- a/modules/database-mongodb/impl/src/main/java/eu/cloudnetservice/modules/mongodb/impl/MongoDBDatabaseProvider.java +++ b/modules/database-mongodb/impl/src/main/java/eu/cloudnetservice/modules/mongodb/impl/MongoDBDatabaseProvider.java @@ -51,6 +51,11 @@ public boolean init() { return true; } + @Override + public boolean synced() { + return true; + } + @Override public @NonNull LocalDatabase database(@NonNull String name) { return this.databaseCache.get(name, $ -> { diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabase.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabase.java index e0721c1d5b..80f5766629 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabase.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabase.java @@ -156,7 +156,7 @@ public long documentCount() { @Override public boolean synced() { - return true; + return this.databaseProvider.synced(); } @Override diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java index a907ef8a72..034a2aa691 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java @@ -18,6 +18,7 @@ import eu.cloudnetservice.modules.sql.config.DatabaseType; import lombok.NonNull; +import org.jetbrains.annotations.UnknownNullability; import org.jooq.SQLDialect; public enum JooqDatabaseType { @@ -34,7 +35,7 @@ public enum JooqDatabaseType { this.jooqDialect = jooqDialect; } - public static @NonNull JooqDatabaseType fromDatabaseType(@NonNull DatabaseType databaseType) { + public static @NonNull JooqDatabaseType fromDatabaseType(@UnknownNullability DatabaseType databaseType) { return switch (databaseType) { case MYSQL -> MYSQL; case MARIADB -> MARIADB; diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java index 239b089d71..08552ac4cb 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java @@ -67,7 +67,7 @@ public boolean init() { hikariConfig.addDataSourceProperty("maintainTimeStats", "false"); hikariConfig.setMinimumIdle(2); - hikariConfig.setMaximumPoolSize(100); + hikariConfig.setMaximumPoolSize(10); hikariConfig.setConnectionTimeout(10_000); hikariConfig.setValidationTimeout(10_000); @@ -76,6 +76,11 @@ public boolean init() { return true; } + @Override + public boolean synced() { + return true; + } + @Override public @NonNull LocalDatabase database(@NonNull String name) { this.tableCreator.createTable(this.dslContext, name); diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java index b97d386be8..65c5a3775e 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java @@ -17,9 +17,7 @@ package eu.cloudnetservice.modules.sql.impl.sqlite; import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; -import eu.cloudnetservice.modules.sql.impl.JooqDatabase; import eu.cloudnetservice.modules.sql.impl.JooqProvider; -import eu.cloudnetservice.node.database.LocalDatabase; import lombok.NonNull; public class SQLiteDatabaseProvider extends JooqProvider { @@ -29,7 +27,7 @@ protected SQLiteDatabaseProvider(@NonNull JooqConfigurationEntry config) { } @Override - public @NonNull LocalDatabase database(@NonNull String name) { - return new JooqDatabase(name, this, super.dslContext); + public boolean synced() { + return false; } } diff --git a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/h2/H2Database.java b/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/h2/H2Database.java index 24c97932d5..ce9e4b9cf2 100644 --- a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/h2/H2Database.java +++ b/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/h2/H2Database.java @@ -18,8 +18,7 @@ import eu.cloudnetservice.driver.document.Document; import eu.cloudnetservice.driver.document.DocumentFactory; -import eu.cloudnetservice.node.impl.database.sql.SQLDatabase; -import eu.cloudnetservice.node.impl.database.sql.SQLDatabaseProvider; +import eu.cloudnetservice.node.impl.database.AbstractDatabase; import java.sql.ResultSet; import java.util.ArrayList; import java.util.Collection; @@ -32,10 +31,18 @@ import lombok.NonNull; import org.jetbrains.annotations.Nullable; -public final class H2Database extends SQLDatabase { +@Deprecated(forRemoval = true) +public final class H2Database extends AbstractDatabase { - public H2Database(@NonNull SQLDatabaseProvider provider, @NonNull String name) { - super(provider, name); + private static final String TABLE_COLUMN_KEY = "Name"; + private static final String TABLE_COLUMN_VAL = "Document"; + + private final H2DatabaseProvider databaseProvider; + + public H2Database(@NonNull H2DatabaseProvider provider, @NonNull String name) { + super(name, provider); + + this.databaseProvider = provider; // create the table provider.executeUpdate(String.format( diff --git a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/h2/H2DatabaseProvider.java b/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/h2/H2DatabaseProvider.java index a9e2ffb444..8e2bb4c565 100644 --- a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/h2/H2DatabaseProvider.java +++ b/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/h2/H2DatabaseProvider.java @@ -17,7 +17,7 @@ package eu.cloudnetservice.node.impl.database.h2; import eu.cloudnetservice.node.database.LocalDatabase; -import eu.cloudnetservice.node.impl.database.sql.SQLDatabaseProvider; +import eu.cloudnetservice.node.impl.database.AbstractNodeDatabaseProvider; import eu.cloudnetservice.utils.base.StringUtil; import eu.cloudnetservice.utils.base.io.FileUtil; import io.vavr.CheckedFunction1; @@ -33,14 +33,19 @@ import org.h2.Driver; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Deprecated(forRemoval = true) -public final class H2DatabaseProvider extends SQLDatabaseProvider { +public final class H2DatabaseProvider extends AbstractNodeDatabaseProvider { static { Driver.load(); } + private static final String[] TABLE_TYPE = new String[]{"TABLE"}; + private static final Logger LOGGER = LoggerFactory.getLogger(H2DatabaseProvider.class); + private final Path h2dbFile; private Connection connection; @@ -49,6 +54,11 @@ public H2DatabaseProvider(@NonNull String h2File) { this.h2dbFile = Path.of(h2File); } + @Override + public boolean synced() { + return false; + } + @Override public boolean init() throws Exception { FileUtil.createDirectory(this.h2dbFile.getParent()); @@ -62,6 +72,17 @@ public boolean init() throws Exception { return this.databaseCache.get(name, $ -> new H2Database(this, name)); } + @Override + public boolean containsDatabase(@NonNull String name) { + for (var database : this.databaseNames()) { + if (database.equalsIgnoreCase(name)) { + return true; + } + } + + return false; + } + @Override public boolean deleteDatabase(@NonNull String name) { return this.executeUpdate("DROP TABLE IF EXISTS `" + name + "`") != -1; @@ -96,14 +117,8 @@ public void close() throws Exception { } } - @Override - public @NonNull Connection connection() { - return this.connection; - } - - @Override public int executeUpdate(@NonNull String query, @NonNull Object... objects) { - try (var preparedStatement = this.connection().prepareStatement(query)) { + try (var preparedStatement = this.connection.prepareStatement(query)) { for (var i = 0; i < objects.length; i++) { preparedStatement.setString(i + 1, objects[i].toString()); } @@ -115,14 +130,13 @@ public int executeUpdate(@NonNull String query, @NonNull Object... objects) { } } - @Override public @UnknownNullability T executeQuery( @NonNull String query, @NonNull CheckedFunction1 callback, @Nullable T def, @NonNull Object... objects ) { - try (var preparedStatement = this.connection().prepareStatement(query)) { + try (var preparedStatement = this.connection.prepareStatement(query)) { for (var i = 0; i < objects.length; i++) { preparedStatement.setString(i + 1, objects[i].toString()); } diff --git a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/sql/SQLDatabase.java b/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/sql/SQLDatabase.java deleted file mode 100644 index 9a4a3409b3..0000000000 --- a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/sql/SQLDatabase.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019-present CloudNetService team & contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.cloudnetservice.node.impl.database.sql; - -import eu.cloudnetservice.node.impl.database.AbstractDatabase; -import lombok.NonNull; -import org.jetbrains.annotations.ApiStatus; - -@Deprecated -@ApiStatus.ScheduledForRemoval(inVersion = "4.1") -public abstract class SQLDatabase extends AbstractDatabase { - - protected static final String TABLE_COLUMN_KEY = "Name"; - protected static final String TABLE_COLUMN_VAL = "Document"; - - protected final SQLDatabaseProvider databaseProvider; - - public SQLDatabase(@NonNull SQLDatabaseProvider provider, @NonNull String name) { - super(name, provider); - this.databaseProvider = provider; - } -} diff --git a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/sql/SQLDatabaseProvider.java b/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/sql/SQLDatabaseProvider.java deleted file mode 100644 index c452740df5..0000000000 --- a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/sql/SQLDatabaseProvider.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2019-present CloudNetService team & contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.cloudnetservice.node.impl.database.sql; - -import com.github.benmanes.caffeine.cache.RemovalListener; -import eu.cloudnetservice.node.database.LocalDatabase; -import eu.cloudnetservice.node.impl.database.AbstractNodeDatabaseProvider; -import io.vavr.CheckedFunction1; -import java.sql.Connection; -import java.sql.ResultSet; -import lombok.NonNull; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Deprecated -@ApiStatus.ScheduledForRemoval(inVersion = "4.1") -public abstract class SQLDatabaseProvider extends AbstractNodeDatabaseProvider { - - protected static final String[] TABLE_TYPE = new String[]{"TABLE"}; - protected static final Logger LOGGER = LoggerFactory.getLogger(SQLDatabaseProvider.class); - - protected SQLDatabaseProvider(@NonNull RemovalListener removalListener) { - super(removalListener); - } - - @Override - public boolean containsDatabase(@NonNull String name) { - for (var database : this.databaseNames()) { - if (database.equalsIgnoreCase(name)) { - return true; - } - } - - return false; - } - - public abstract @NonNull Connection connection(); - - public abstract int executeUpdate(@NonNull String query, @NonNull Object... objects); - - public abstract @UnknownNullability T executeQuery( - @NonNull String query, - @NonNull CheckedFunction1 callback, - @Nullable T def, - @NonNull Object... objects); -} diff --git a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/xodus/XodusDatabaseProvider.java b/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/xodus/XodusDatabaseProvider.java index b79ea9ec7a..b62f8a9436 100644 --- a/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/xodus/XodusDatabaseProvider.java +++ b/node/impl/src/main/java/eu/cloudnetservice/node/impl/database/xodus/XodusDatabaseProvider.java @@ -63,6 +63,11 @@ public boolean init() { return true; } + @Override + public boolean synced() { + return false; + } + @Override public @NonNull LocalDatabase database(@NonNull String name) { return this.databaseCache.get(name, $ -> this.environment.computeInTransaction(txn -> { From f48fe966786bbc4cffc206d097a24c6e8c593bbe Mon Sep 17 00:00:00 2001 From: 0utplay Date: Thu, 29 Jan 2026 20:47:40 +0100 Subject: [PATCH 07/13] chore: unify old database classes --- ...nEntry.java => SQLConfigurationEntry.java} | 2 +- .../sql/config/SQLModuleConfiguration.java | 2 +- .../sql/impl/CloudNetSQLDatabaseModule.java | 53 ++++++++---------- .../modules/sql/impl/JooqDatabaseType.java | 55 +++++++++++++++++-- .../modules/sql/impl/JooqProvider.java | 27 ++++++--- .../sql/impl/table/MariaDBTableCreator.java | 43 +++++++++++++++ .../sql/impl/table/MySQLTableCreator.java | 43 +++++++++++++++ .../impl/table/PostgreSQLTableCreator.java | 43 +++++++++++++++ .../SQLiteTableCreator.java} | 28 ++++++---- .../sql/impl/{ => table}/TableCreator.java | 8 ++- .../modules/sql/impl/MariaDBDatabaseTest.java | 6 +- .../modules/sql/impl/MySQLDBDatabaseTest.java | 6 +- .../sql/impl/PostgreSQLDatabaseTest.java | 6 +- .../modules/sql/impl/SQLiteDatabaseTest.java | 6 +- 14 files changed, 256 insertions(+), 72 deletions(-) rename modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/{JooqConfigurationEntry.java => SQLConfigurationEntry.java} (98%) create mode 100644 modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/MariaDBTableCreator.java create mode 100644 modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/MySQLTableCreator.java create mode 100644 modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/PostgreSQLTableCreator.java rename modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/{sqlite/SQLiteDatabaseProvider.java => table/SQLiteTableCreator.java} (50%) rename modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/{ => table}/TableCreator.java (77%) diff --git a/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/JooqConfigurationEntry.java b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLConfigurationEntry.java similarity index 98% rename from modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/JooqConfigurationEntry.java rename to modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLConfigurationEntry.java index 23ff4c2630..0eef16060e 100644 --- a/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/JooqConfigurationEntry.java +++ b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLConfigurationEntry.java @@ -21,7 +21,7 @@ import lombok.NonNull; import org.jetbrains.annotations.Nullable; -public record JooqConfigurationEntry( +public record SQLConfigurationEntry( @NonNull DatabaseType databaseType, @NonNull String databaseServiceName, @Nullable String databaseName, diff --git a/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLModuleConfiguration.java b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLModuleConfiguration.java index daa58eeaac..cf2f2ed772 100644 --- a/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLModuleConfiguration.java +++ b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLModuleConfiguration.java @@ -19,6 +19,6 @@ import java.util.List; import lombok.NonNull; -public record SQLModuleConfiguration(@NonNull List entries) { +public record SQLModuleConfiguration(@NonNull List entries) { } diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetSQLDatabaseModule.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetSQLDatabaseModule.java index 76930c4f99..858a3f85f4 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetSQLDatabaseModule.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetSQLDatabaseModule.java @@ -24,7 +24,7 @@ import eu.cloudnetservice.driver.network.HostAndPort; import eu.cloudnetservice.driver.registry.ServiceRegistry; import eu.cloudnetservice.modules.sql.config.DatabaseType; -import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.config.SQLConfigurationEntry; import eu.cloudnetservice.modules.sql.config.SQLModuleConfiguration; import eu.cloudnetservice.node.impl.database.NodeDatabaseProvider; import io.leangen.geantyref.TypeFactory; @@ -32,8 +32,6 @@ import java.util.List; import lombok.NonNull; import org.jetbrains.annotations.Nullable; -import org.jooq.impl.DSL; -import org.jooq.impl.SQLDataType; @Singleton public final class CloudNetSQLDatabaseModule extends DriverModule { @@ -50,43 +48,42 @@ public void convertConfig() { var serviceName = config.getString("databaseServiceName"); var username = config.getString("username"); var password = config.getString("password"); + + String database; + HostAndPort address; if (config.contains("addresses")) { List addresses = config.readObject( "addresses", TypeFactory.parameterizedClass(List.class, LegacyConnectionEndpoint.class)); - var convertedConfig = new JooqConfigurationEntry( - DatabaseType.MYSQL, - serviceName, - config.getString("database"), - username, - password, - addresses.isEmpty() ? new HostAndPort("127.0.0.1", 3306) : addresses.getFirst().address(), - null); - this.writeConfig(Document.newJsonDocument().appendTree(convertedConfig)); - } else if (config.contains("endpoints")) { + database = config.getString("database"); + address = addresses.isEmpty() ? new HostAndPort("127.0.0.1", 3306) : addresses.getFirst().address(); + } else { List endpoints = config.readObject( "endpoints", TypeFactory.parameterizedClass(List.class, LegacyConnectionEndpoint.class)); var endpoint = endpoints.isEmpty() ? new LegacyConnectionEndpoint("cloudnet", new HostAndPort("127.0.0.1", 3306)) : endpoints.getFirst(); - var convertedConfig = new JooqConfigurationEntry( - DatabaseType.MYSQL, - serviceName, - endpoint.database(), - username, - password, - endpoint.address(), - null); - this.writeConfig(Document.newJsonDocument().appendTree(convertedConfig)); + database = endpoint.database(); + address = endpoint.address(); } + + var convertedConfig = new SQLConfigurationEntry( + DatabaseType.MYSQL, + serviceName, + database, + username, + password, + address, + null); + this.writeConfig(Document.newJsonDocument().appendTree(convertedConfig)); } @ModuleTask(order = 125, lifecycle = ModuleLifeCycle.LOADED) public void registerDatabaseProvider(@NonNull ServiceRegistry serviceRegistry) { this.configuration = this.readConfig( SQLModuleConfiguration.class, - () -> new SQLModuleConfiguration(List.of(new JooqConfigurationEntry( + () -> new SQLModuleConfiguration(List.of(new SQLConfigurationEntry( DatabaseType.MAGIC_MIKE, "sql", "cloudnet", @@ -98,17 +95,11 @@ public void registerDatabaseProvider(@NonNull ServiceRegistry serviceRegistry) { DocumentFactory.json()); for (var entry : this.configuration.entries()) { + var databaseType = JooqDatabaseType.fromDatabaseType(entry.databaseType()); serviceRegistry.registerProvider( NodeDatabaseProvider.class, entry.databaseServiceName(), - new JooqProvider(((dslContext, name) -> { - dslContext.createTableIfNotExists(DSL.name(name)) - .column(JooqDatabase.KEY_FIELD, SQLDataType.VARCHAR(512) - .notNull() - .collation(DSL.collation("utf8mb4_bin"))) - .column(JooqDatabase.DOCUMENT_FIELD, SQLDataType.JSONB.notNull()) - .execute(); - }), entry)); + databaseType.createProvider(entry)); } } diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java index 034a2aa691..cd4348560c 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java @@ -17,22 +17,57 @@ package eu.cloudnetservice.modules.sql.impl; import eu.cloudnetservice.modules.sql.config.DatabaseType; +import eu.cloudnetservice.modules.sql.config.SQLConfigurationEntry; +import eu.cloudnetservice.modules.sql.impl.table.MariaDBTableCreator; +import eu.cloudnetservice.modules.sql.impl.table.MySQLTableCreator; +import eu.cloudnetservice.modules.sql.impl.table.PostgreSQLTableCreator; +import eu.cloudnetservice.modules.sql.impl.table.SQLiteTableCreator; +import eu.cloudnetservice.modules.sql.impl.table.TableCreator; import lombok.NonNull; import org.jetbrains.annotations.UnknownNullability; import org.jooq.SQLDialect; public enum JooqDatabaseType { - MYSQL("com.mysql.cj.jdbc.Driver", SQLDialect.MYSQL), - MARIADB("org.mariadb.jdbc.Driver", SQLDialect.MARIADB), - POSTGRESQL("org.postgresql.Driver", SQLDialect.POSTGRES), - SQLITE("org.sqlite.JDBC", SQLDialect.SQLITE); + MYSQL( + "com.mysql.cj.jdbc.Driver", + SQLDialect.MYSQL, + true, + new MySQLTableCreator() + ), + MARIADB( + "org.mariadb.jdbc.Driver", + SQLDialect.MARIADB, + true, + new MariaDBTableCreator() + ), + POSTGRESQL( + "org.postgresql.Driver", + SQLDialect.POSTGRES, + true, + new PostgreSQLTableCreator() + ), + SQLITE( + "org.sqlite.JDBC", + SQLDialect.SQLITE, + false, + new SQLiteTableCreator() + ); private final String driverClassName; private final SQLDialect jooqDialect; + private final boolean synced; + private final TableCreator tableCreator; - JooqDatabaseType(@NonNull String driverClassName, @NonNull SQLDialect jooqDialect) { + JooqDatabaseType( + @NonNull String driverClassName, + @NonNull SQLDialect jooqDialect, + boolean synced, + @NonNull TableCreator tableCreator + ) { this.driverClassName = driverClassName; this.jooqDialect = jooqDialect; + this.synced = synced; + this.tableCreator = tableCreator; } public static @NonNull JooqDatabaseType fromDatabaseType(@UnknownNullability DatabaseType databaseType) { @@ -41,7 +76,7 @@ public enum JooqDatabaseType { case MARIADB -> MARIADB; case POSTGRES -> POSTGRESQL; case SQLITE -> SQLITE; - case MAGIC_MIKE -> throw new IllegalArgumentException("magic mix"); + case MAGIC_MIKE -> throw new IllegalArgumentException("magic mike"); }; } @@ -52,4 +87,12 @@ public enum JooqDatabaseType { public @NonNull SQLDialect jooqDialect() { return this.jooqDialect; } + + public boolean synced() { + return this.synced; + } + + public @NonNull JooqProvider createProvider(@NonNull SQLConfigurationEntry config) { + return new JooqProvider(this.tableCreator, this, config); + } } diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java index 08552ac4cb..03648e29bf 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java @@ -18,7 +18,8 @@ import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; -import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.config.SQLConfigurationEntry; +import eu.cloudnetservice.modules.sql.impl.table.TableCreator; import eu.cloudnetservice.node.database.LocalDatabase; import eu.cloudnetservice.node.impl.database.AbstractNodeDatabaseProvider; import java.util.Collection; @@ -31,15 +32,21 @@ public class JooqProvider extends AbstractNodeDatabaseProvider { protected final TableCreator tableCreator; - protected final JooqConfigurationEntry config; + protected final JooqDatabaseType databaseType; + protected final SQLConfigurationEntry config; protected DSLContext dslContext; protected HikariDataSource dataSource; - protected JooqProvider(@NonNull TableCreator tableCreator, @NonNull JooqConfigurationEntry config) { + protected JooqProvider( + @NonNull TableCreator tableCreator, + @NonNull JooqDatabaseType databaseType, + @NonNull SQLConfigurationEntry config + ) { super(DEFAULT_REMOVAL_LISTENER); this.tableCreator = tableCreator; + this.databaseType = databaseType; this.config = config; } @@ -48,10 +55,8 @@ public boolean init() { var hikariConfig = new HikariConfig(); var endpoint = this.config.buildConnectionUri(); - var databaseType = JooqDatabaseType.fromDatabaseType(this.config.databaseType()); - hikariConfig.setJdbcUrl(endpoint); - hikariConfig.setDriverClassName(databaseType.driverClassName()); + hikariConfig.setDriverClassName(this.databaseType.driverClassName()); hikariConfig.setUsername(this.config.username()); hikariConfig.setPassword(this.config.password()); @@ -72,18 +77,22 @@ public boolean init() { hikariConfig.setValidationTimeout(10_000); this.dataSource = new HikariDataSource(hikariConfig); - this.dslContext = DSL.using(this.dataSource, databaseType.jooqDialect()); + this.dslContext = DSL.using(this.dataSource, this.databaseType.jooqDialect()); return true; } @Override public boolean synced() { - return true; + return this.databaseType.synced(); } @Override public @NonNull LocalDatabase database(@NonNull String name) { - this.tableCreator.createTable(this.dslContext, name); + this.tableCreator.createTable( + this.dslContext, + name, + JooqDatabase.KEY_FIELD, + JooqDatabase.DOCUMENT_FIELD); return new JooqDatabase(name, this, this.dslContext); } diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/MariaDBTableCreator.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/MariaDBTableCreator.java new file mode 100644 index 0000000000..48864a3a4a --- /dev/null +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/MariaDBTableCreator.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.sql.impl.table; + +import eu.cloudnetservice.driver.document.Document; +import lombok.NonNull; +import org.jooq.DSLContext; +import org.jooq.Field; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; + +public class MariaDBTableCreator implements TableCreator { + + @Override + public void createTable( + @NonNull DSLContext dslContext, + @NonNull String name, + @NonNull Field keyField, + @NonNull Field documentField + ) { + dslContext.createTableIfNotExists(DSL.name(name)) + .column(keyField, SQLDataType.VARCHAR(512) + .notNull() + .collation(DSL.collation("utf8mb4_bin"))) + .column(documentField, SQLDataType.JSONB.notNull()) + .primaryKey(keyField) + .execute(); + } +} diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/MySQLTableCreator.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/MySQLTableCreator.java new file mode 100644 index 0000000000..35971b01e6 --- /dev/null +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/MySQLTableCreator.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.sql.impl.table; + +import eu.cloudnetservice.driver.document.Document; +import lombok.NonNull; +import org.jooq.DSLContext; +import org.jooq.Field; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; + +public class MySQLTableCreator implements TableCreator { + + @Override + public void createTable( + @NonNull DSLContext dslContext, + @NonNull String name, + @NonNull Field keyField, + @NonNull Field documentField + ) { + dslContext.createTableIfNotExists(DSL.name(name)) + .column(keyField, SQLDataType.VARCHAR(512) + .notNull() + .collation(DSL.collation("utf8mb4_bin"))) + .column(documentField, SQLDataType.JSONB.notNull()) + .primaryKey(keyField) + .execute(); + } +} diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/PostgreSQLTableCreator.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/PostgreSQLTableCreator.java new file mode 100644 index 0000000000..04b7765965 --- /dev/null +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/PostgreSQLTableCreator.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.modules.sql.impl.table; + +import eu.cloudnetservice.driver.document.Document; +import lombok.NonNull; +import org.jooq.DSLContext; +import org.jooq.Field; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; + +public class PostgreSQLTableCreator implements TableCreator { + + @Override + public void createTable( + @NonNull DSLContext dslContext, + @NonNull String name, + @NonNull Field keyField, + @NonNull Field documentField + ) { + dslContext.createTableIfNotExists(DSL.name(name)) + .column(keyField, SQLDataType.VARCHAR(512).notNull()) + .column(documentField, SQLDataType.JSONB.notNull()) + .primaryKey(keyField) + .execute(); + + dslContext.execute("CREATE INDEX IF NOT EXISTS ") + } +} diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/SQLiteTableCreator.java similarity index 50% rename from modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java rename to modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/SQLiteTableCreator.java index 65c5a3775e..c6669b17ec 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/sqlite/SQLiteDatabaseProvider.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/SQLiteTableCreator.java @@ -14,20 +14,28 @@ * limitations under the License. */ -package eu.cloudnetservice.modules.sql.impl.sqlite; +package eu.cloudnetservice.modules.sql.impl.table; -import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; -import eu.cloudnetservice.modules.sql.impl.JooqProvider; +import eu.cloudnetservice.driver.document.Document; import lombok.NonNull; +import org.jooq.DSLContext; +import org.jooq.Field; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; -public class SQLiteDatabaseProvider extends JooqProvider { - - protected SQLiteDatabaseProvider(@NonNull JooqConfigurationEntry config) { - super((_, _) -> {}, config); - } +public class SQLiteTableCreator implements TableCreator { @Override - public boolean synced() { - return false; + public void createTable( + @NonNull DSLContext dslContext, + @NonNull String name, + @NonNull Field keyField, + @NonNull Field documentField + ) { + dslContext.createTableIfNotExists(DSL.name(name)) + .column(keyField, SQLDataType.VARCHAR(512).notNull()) + .column(documentField, SQLDataType.JSON.notNull()) + .primaryKey(keyField) + .execute(); } } diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/TableCreator.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/TableCreator.java similarity index 77% rename from modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/TableCreator.java rename to modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/TableCreator.java index 3f89baafdc..d3f2915276 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/TableCreator.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/TableCreator.java @@ -14,16 +14,20 @@ * limitations under the License. */ -package eu.cloudnetservice.modules.sql.impl; +package eu.cloudnetservice.modules.sql.impl.table; +import eu.cloudnetservice.driver.document.Document; import lombok.NonNull; import org.jooq.DSLContext; +import org.jooq.Field; @FunctionalInterface public interface TableCreator { void createTable( @NonNull DSLContext dslContext, - @NonNull String name); + @NonNull String name, + @NonNull Field keyField, + @NonNull Field documentField); } diff --git a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java index b5978aae71..21cd31875f 100644 --- a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java @@ -18,7 +18,7 @@ import eu.cloudnetservice.driver.network.HostAndPort; import eu.cloudnetservice.modules.sql.config.DatabaseType; -import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.config.SQLConfigurationEntry; import eu.cloudnetservice.modules.sql.impl.junit.EnableServicesInject; import org.junit.jupiter.api.BeforeAll; import org.testcontainers.containers.GenericContainer; @@ -40,7 +40,7 @@ public class MariaDBDatabaseTest extends SQLDatabaseTest { @BeforeAll static void setup() throws Exception { - var config = new JooqConfigurationEntry( + var config = new SQLConfigurationEntry( DatabaseType.MARIADB, "mariadb", "cn_testing", @@ -48,7 +48,7 @@ static void setup() throws Exception { "test", new HostAndPort(mariaContainer.getHost(), mariaContainer.getFirstMappedPort()), null); - databaseProvider = new JooqProvider(config); + databaseProvider = JooqDatabaseType.MARIADB.createProvider(config); databaseProvider.init(); } } diff --git a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java index b8fb02d51c..7a0df02ad9 100644 --- a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java @@ -18,7 +18,7 @@ import eu.cloudnetservice.driver.network.HostAndPort; import eu.cloudnetservice.modules.sql.config.DatabaseType; -import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.config.SQLConfigurationEntry; import eu.cloudnetservice.modules.sql.impl.junit.EnableServicesInject; import org.junit.jupiter.api.BeforeAll; import org.testcontainers.containers.GenericContainer; @@ -39,7 +39,7 @@ public class MySQLDBDatabaseTest extends SQLDatabaseTest { @BeforeAll static void setup() throws Exception { - var config = new JooqConfigurationEntry( + var config = new SQLConfigurationEntry( DatabaseType.MYSQL, "mysql", "cn_testing", @@ -47,7 +47,7 @@ static void setup() throws Exception { "test", new HostAndPort(mysqlContainer.getHost(), mysqlContainer.getFirstMappedPort()), null); - databaseProvider = new JooqProvider(config); + databaseProvider = JooqDatabaseType.MYSQL.createProvider(config); databaseProvider.init(); } } diff --git a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java index d498bde641..58bf23c296 100644 --- a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java @@ -18,7 +18,7 @@ import eu.cloudnetservice.driver.network.HostAndPort; import eu.cloudnetservice.modules.sql.config.DatabaseType; -import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.config.SQLConfigurationEntry; import eu.cloudnetservice.modules.sql.impl.junit.EnableServicesInject; import org.junit.jupiter.api.BeforeAll; import org.testcontainers.containers.GenericContainer; @@ -39,7 +39,7 @@ public class PostgreSQLDatabaseTest extends SQLDatabaseTest { @BeforeAll static void setup() throws Exception { - var config = new JooqConfigurationEntry( + var config = new SQLConfigurationEntry( DatabaseType.POSTGRES, "postgres", "cn_testing", @@ -47,7 +47,7 @@ static void setup() throws Exception { "test", new HostAndPort(postgresContainer.getHost(), postgresContainer.getFirstMappedPort()), null); - databaseProvider = new JooqProvider(config); + databaseProvider = JooqDatabaseType.POSTGRESQL.createProvider(config); databaseProvider.init(); } } diff --git a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java index 0da298ac6c..9d031e8710 100644 --- a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java @@ -17,7 +17,7 @@ package eu.cloudnetservice.modules.sql.impl; import eu.cloudnetservice.modules.sql.config.DatabaseType; -import eu.cloudnetservice.modules.sql.config.JooqConfigurationEntry; +import eu.cloudnetservice.modules.sql.config.SQLConfigurationEntry; import eu.cloudnetservice.modules.sql.impl.junit.EnableServicesInject; import org.junit.jupiter.api.BeforeAll; @@ -26,7 +26,7 @@ public class SQLiteDatabaseTest extends SQLDatabaseTest { @BeforeAll static void setup() throws Exception { - var config = new JooqConfigurationEntry( + var config = new SQLConfigurationEntry( DatabaseType.SQLITE, "sqlite", "cn_testing", @@ -34,7 +34,7 @@ static void setup() throws Exception { null, null, "jdbc:sqlite::memory:"); - databaseProvider = new JooqProvider(config); + databaseProvider = JooqDatabaseType.SQLITE.createProvider(config); databaseProvider.init(); } } From d974082606e13ea5c966cffa245fe6b8c94ffe39 Mon Sep 17 00:00:00 2001 From: 0utplay Date: Fri, 7 Aug 2026 20:39:15 +0200 Subject: [PATCH 08/13] chore: remove magic mike --- .../modules/sql/config/DatabaseType.java | 1 - .../sql/config/SQLConfigurationEntry.java | 2 +- .../sql/impl/CloudNetSQLDatabaseModule.java | 16 +++++++++++++++- .../modules/sql/impl/JooqDatabaseType.java | 1 - .../modules/sql/impl/MariaDBDatabaseTest.java | 1 + .../modules/sql/impl/MySQLDBDatabaseTest.java | 1 + .../modules/sql/impl/PostgreSQLDatabaseTest.java | 1 + .../modules/sql/impl/SQLiteDatabaseTest.java | 1 + 8 files changed, 20 insertions(+), 4 deletions(-) diff --git a/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/DatabaseType.java b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/DatabaseType.java index fea9ad711e..6dce28accc 100644 --- a/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/DatabaseType.java +++ b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/DatabaseType.java @@ -17,7 +17,6 @@ package eu.cloudnetservice.modules.sql.config; public enum DatabaseType { - MAGIC_MIKE, POSTGRES, MYSQL, MARIADB, diff --git a/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLConfigurationEntry.java b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLConfigurationEntry.java index 0eef16060e..4b410a1f30 100644 --- a/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLConfigurationEntry.java +++ b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLConfigurationEntry.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.Nullable; public record SQLConfigurationEntry( + boolean enabled, @NonNull DatabaseType databaseType, @NonNull String databaseServiceName, @Nullable String databaseName, @@ -45,7 +46,6 @@ public record SQLConfigurationEntry( var jdbcDescriptor = switch (this.databaseType) { case MYSQL -> "mysql"; case MARIADB -> "mariadb"; - case MAGIC_MIKE -> throw new IllegalArgumentException("magic mike"); case POSTGRES -> "postgresql"; case SQLITE -> "sqlite"; }; diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetSQLDatabaseModule.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetSQLDatabaseModule.java index 858a3f85f4..9e30ccf7f5 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetSQLDatabaseModule.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/CloudNetSQLDatabaseModule.java @@ -69,6 +69,7 @@ public void convertConfig() { } var convertedConfig = new SQLConfigurationEntry( + true, DatabaseType.MYSQL, serviceName, database, @@ -84,7 +85,8 @@ public void registerDatabaseProvider(@NonNull ServiceRegistry serviceRegistry) { this.configuration = this.readConfig( SQLModuleConfiguration.class, () -> new SQLModuleConfiguration(List.of(new SQLConfigurationEntry( - DatabaseType.MAGIC_MIKE, + false, + DatabaseType.MYSQL, "sql", "cloudnet", "cloudnet", @@ -95,6 +97,10 @@ public void registerDatabaseProvider(@NonNull ServiceRegistry serviceRegistry) { DocumentFactory.json()); for (var entry : this.configuration.entries()) { + if (!entry.enabled()) { + continue; + } + var databaseType = JooqDatabaseType.fromDatabaseType(entry.databaseType()); serviceRegistry.registerProvider( NodeDatabaseProvider.class, @@ -105,7 +111,15 @@ public void registerDatabaseProvider(@NonNull ServiceRegistry serviceRegistry) { @ModuleTask(order = 127, lifecycle = ModuleLifeCycle.STOPPED) public void unregisterDatabaseProvider(@NonNull ServiceRegistry serviceRegistry) { + if (this.configuration == null) { + return; + } + for (var entry : this.configuration.entries()) { + if (!entry.enabled()) { + continue; + } + var service = serviceRegistry.registration(NodeDatabaseProvider.class, entry.databaseServiceName()); if (service != null) { service.unregister(); diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java index cd4348560c..ed434c6db0 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqDatabaseType.java @@ -76,7 +76,6 @@ public enum JooqDatabaseType { case MARIADB -> MARIADB; case POSTGRES -> POSTGRESQL; case SQLITE -> SQLITE; - case MAGIC_MIKE -> throw new IllegalArgumentException("magic mike"); }; } diff --git a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java index 21cd31875f..d36360d7f4 100644 --- a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java @@ -41,6 +41,7 @@ public class MariaDBDatabaseTest extends SQLDatabaseTest { @BeforeAll static void setup() throws Exception { var config = new SQLConfigurationEntry( + true, DatabaseType.MARIADB, "mariadb", "cn_testing", diff --git a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java index 7a0df02ad9..9c4249721d 100644 --- a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java @@ -40,6 +40,7 @@ public class MySQLDBDatabaseTest extends SQLDatabaseTest { @BeforeAll static void setup() throws Exception { var config = new SQLConfigurationEntry( + true, DatabaseType.MYSQL, "mysql", "cn_testing", diff --git a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java index 58bf23c296..33f582dc42 100644 --- a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java @@ -40,6 +40,7 @@ public class PostgreSQLDatabaseTest extends SQLDatabaseTest { @BeforeAll static void setup() throws Exception { var config = new SQLConfigurationEntry( + true, DatabaseType.POSTGRES, "postgres", "cn_testing", diff --git a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java index 9d031e8710..fe430ef86b 100644 --- a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/SQLiteDatabaseTest.java @@ -27,6 +27,7 @@ public class SQLiteDatabaseTest extends SQLDatabaseTest { @BeforeAll static void setup() throws Exception { var config = new SQLConfigurationEntry( + true, DatabaseType.SQLITE, "sqlite", "cn_testing", From bd668b9a87a977e4e5fac377b99280fb2a2f2a93 Mon Sep 17 00:00:00 2001 From: 0utplay Date: Fri, 7 Aug 2026 20:51:31 +0200 Subject: [PATCH 09/13] chore: dings --- .../cloudnetservice/driver/database/Database.java | 1 - .../modules/sql/impl/JooqProvider.java | 14 ++++++++------ .../sql/impl/table/PostgreSQLTableCreator.java | 2 -- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/driver/api/src/main/java/eu/cloudnetservice/driver/database/Database.java b/driver/api/src/main/java/eu/cloudnetservice/driver/database/Database.java index 4023a42b13..5fe4b100ce 100644 --- a/driver/api/src/main/java/eu/cloudnetservice/driver/database/Database.java +++ b/driver/api/src/main/java/eu/cloudnetservice/driver/database/Database.java @@ -306,7 +306,6 @@ public interface Database extends Named, AutoCloseable { @NonNull CompletableFuture clearAsync(); - /** /** * Get the amount of key-value pairs currently stored in the database. *

diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java index 03648e29bf..b14bf884bb 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java @@ -88,12 +88,14 @@ public boolean synced() { @Override public @NonNull LocalDatabase database(@NonNull String name) { - this.tableCreator.createTable( - this.dslContext, - name, - JooqDatabase.KEY_FIELD, - JooqDatabase.DOCUMENT_FIELD); - return new JooqDatabase(name, this, this.dslContext); + return this.databaseCache.get(name, _ -> { + this.tableCreator.createTable( + this.dslContext, + name, + JooqDatabase.KEY_FIELD, + JooqDatabase.DOCUMENT_FIELD); + return new JooqDatabase(name, this, this.dslContext); + }); } @Override diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/PostgreSQLTableCreator.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/PostgreSQLTableCreator.java index 04b7765965..1fbfaa1844 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/PostgreSQLTableCreator.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/table/PostgreSQLTableCreator.java @@ -37,7 +37,5 @@ public void createTable( .column(documentField, SQLDataType.JSONB.notNull()) .primaryKey(keyField) .execute(); - - dslContext.execute("CREATE INDEX IF NOT EXISTS ") } } From 34ba8d5c7bf5a36b0dd3e2784f3d1059690cf8bf Mon Sep 17 00:00:00 2001 From: 0utplay Date: Fri, 7 Aug 2026 20:57:43 +0200 Subject: [PATCH 10/13] chore: fix nonnull anno --- .../cloudnetservice/modules/sql/impl/DocumentConverter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/DocumentConverter.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/DocumentConverter.java index 4deeb64489..c5d27fd93a 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/DocumentConverter.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/DocumentConverter.java @@ -18,7 +18,7 @@ import eu.cloudnetservice.driver.document.Document; import eu.cloudnetservice.driver.document.DocumentFactory; -import org.jetbrains.annotations.NotNull; +import lombok.NonNull; import org.jooq.Converter; import org.jooq.JSONB; @@ -40,12 +40,12 @@ public JSONB to(Document userObject) { } @Override - public @NotNull Class fromType() { + public @NonNull Class fromType() { return JSONB.class; } @Override - public @NotNull Class toType() { + public @NonNull Class toType() { return Document.class; } } From cac70f302d85196a483dfc1546d06efa1ec7d1cb Mon Sep 17 00:00:00 2001 From: 0utplay Date: Sat, 8 Aug 2026 22:14:31 +0200 Subject: [PATCH 11/13] chore: config messages --- .../modules/sql/config/SQLConfigurationEntry.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLConfigurationEntry.java b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLConfigurationEntry.java index 4b410a1f30..677e84cd0a 100644 --- a/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLConfigurationEntry.java +++ b/modules/database-sql/api/src/main/java/eu/cloudnetservice/modules/sql/config/SQLConfigurationEntry.java @@ -39,10 +39,10 @@ public record SQLConfigurationEntry( return this.overrideConnectionUri; } - Objects.requireNonNull(this.address, ""); - Objects.requireNonNull(this.username, ""); - Objects.requireNonNull(this.password, ""); - Objects.requireNonNull(this.databaseName, ""); + Objects.requireNonNull(this.address, "Address must be set if no override connection uri is set"); + Objects.requireNonNull(this.username, "Username must be set if no override connection uri is set"); + Objects.requireNonNull(this.password, "Password must be set if no override connection uri is set"); + Objects.requireNonNull(this.databaseName, "Database name must be set if no override connection uri is set"); var jdbcDescriptor = switch (this.databaseType) { case MYSQL -> "mysql"; case MARIADB -> "mariadb"; From 13e9e2a43e740997213c26a9907711ebc681f27a Mon Sep 17 00:00:00 2001 From: 0utplay Date: Sat, 8 Aug 2026 22:20:57 +0200 Subject: [PATCH 12/13] chore: invalidate db cache on deletion --- .../java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java index b14bf884bb..8d2f6196f5 100644 --- a/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java +++ b/modules/database-sql/impl/src/main/java/eu/cloudnetservice/modules/sql/impl/JooqProvider.java @@ -105,6 +105,7 @@ public boolean containsDatabase(@NonNull String name) { @Override public boolean deleteDatabase(@NonNull String name) { + this.databaseCache.invalidate(name); return this.dslContext.dropTableIfExists(name).execute() != -1; } From 8e818179c8534cb1aacbc8ba472c001921810516 Mon Sep 17 00:00:00 2001 From: 0utplay Date: Mon, 10 Aug 2026 17:40:08 +0200 Subject: [PATCH 13/13] fix: danke checkstyle, freund und helfer --- .../cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java | 4 ++-- .../cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java | 4 ++-- .../modules/sql/impl/PostgreSQLDatabaseTest.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java index d36360d7f4..e5cee93af5 100644 --- a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MariaDBDatabaseTest.java @@ -30,7 +30,7 @@ public class MariaDBDatabaseTest extends SQLDatabaseTest { @Container - private static final GenericContainer mariaContainer = new GenericContainer<>("mariadb:latest") + private static final GenericContainer MARIA_CONTAINER = new GenericContainer<>("mariadb:latest") .withExposedPorts(3306) .withEnv("MYSQL_USER", "test") .withEnv("MYSQL_PASSWORD", "test") @@ -47,7 +47,7 @@ static void setup() throws Exception { "cn_testing", "test", "test", - new HostAndPort(mariaContainer.getHost(), mariaContainer.getFirstMappedPort()), + new HostAndPort(MARIA_CONTAINER.getHost(), MARIA_CONTAINER.getFirstMappedPort()), null); databaseProvider = JooqDatabaseType.MARIADB.createProvider(config); databaseProvider.init(); diff --git a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java index 9c4249721d..65071a2636 100644 --- a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/MySQLDBDatabaseTest.java @@ -30,7 +30,7 @@ public class MySQLDBDatabaseTest extends SQLDatabaseTest { @Container - private static final GenericContainer mysqlContainer = new GenericContainer<>("mysql:latest") + private static final GenericContainer MYSQL_CONTAINER = new GenericContainer<>("mysql:latest") .withExposedPorts(3306) .withEnv("MYSQL_USER", "test") .withEnv("MYSQL_PASSWORD", "test") @@ -46,7 +46,7 @@ static void setup() throws Exception { "cn_testing", "test", "test", - new HostAndPort(mysqlContainer.getHost(), mysqlContainer.getFirstMappedPort()), + new HostAndPort(MYSQL_CONTAINER.getHost(), MYSQL_CONTAINER.getFirstMappedPort()), null); databaseProvider = JooqDatabaseType.MYSQL.createProvider(config); databaseProvider.init(); diff --git a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java index 33f582dc42..98401d8212 100644 --- a/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java +++ b/modules/database-sql/impl/src/test/java/eu/cloudnetservice/modules/sql/impl/PostgreSQLDatabaseTest.java @@ -30,7 +30,7 @@ public class PostgreSQLDatabaseTest extends SQLDatabaseTest { @Container - private static final GenericContainer postgresContainer = new GenericContainer<>("postgres:latest") + private static final GenericContainer POSTGRES_CONTAINER = new GenericContainer<>("postgres:latest") .withExposedPorts(5432) .withEnv("POSTGRES_USER", "test") .withEnv("POSTGRES_PASSWORD", "test") @@ -46,7 +46,7 @@ static void setup() throws Exception { "cn_testing", "test", "test", - new HostAndPort(postgresContainer.getHost(), postgresContainer.getFirstMappedPort()), + new HostAndPort(POSTGRES_CONTAINER.getHost(), POSTGRES_CONTAINER.getFirstMappedPort()), null); databaseProvider = JooqDatabaseType.POSTGRESQL.createProvider(config); databaseProvider.init();