diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 5a9cba69083..284e2c561fb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -60,6 +60,7 @@ body: - Solace - Solr - TiDB + - Timeplus - ToxiProxy - Trino - Vault diff --git a/.github/ISSUE_TEMPLATE/enhancement.yaml b/.github/ISSUE_TEMPLATE/enhancement.yaml index c6fb9767f60..2ca7de6bc3c 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yaml +++ b/.github/ISSUE_TEMPLATE/enhancement.yaml @@ -60,6 +60,7 @@ body: - Solace - Solr - TiDB + - Timeplus - ToxiProxy - Trino - Vault diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml index 485e1477a06..311ec8ca2c6 100644 --- a/.github/ISSUE_TEMPLATE/feature.yaml +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -60,6 +60,7 @@ body: - Solace - Solr - TiDB + - Timeplus - ToxiProxy - Trino - Vault diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6bdca459fab..0239be10168 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -342,6 +342,11 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 10 + - package-ecosystem: "gradle" + directory: "/modules/timeplus" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 - package-ecosystem: "gradle" directory: "/modules/toxiproxy" schedule: diff --git a/.github/labeler.yml b/.github/labeler.yml index f449a11685d..875e996c428 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -216,6 +216,10 @@ - changed-files: - any-glob-to-any-file: - modules/tidb/**/* +"modules/timeplus": + - changed-files: + - any-glob-to-any-file: + - modules/timeplus/**/* "modules/toxiproxy": - changed-files: - any-glob-to-any-file: diff --git a/.github/settings.yml b/.github/settings.yml index 2bd2289af79..06b1c06e72f 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -253,6 +253,9 @@ labels: - name: modules/tidb color: '#006b75' + - name: modules/timeplus + color: '#006b75' + - name: modules/toxiproxy color: '#006b75' diff --git a/docs/modules/databases/jdbc.md b/docs/modules/databases/jdbc.md index 3c92e8a99ad..90183228bd5 100644 --- a/docs/modules/databases/jdbc.md +++ b/docs/modules/databases/jdbc.md @@ -87,6 +87,10 @@ Insert `tc:` after `jdbc:` as follows. Note that the hostname, port and database `jdbc:tc:tidb:v6.1.0:///databasename` +#### Using Timeplus + +`jdbc:tc:timeplus:2.3.21:///databasename` + #### Using Trino `jdbc:tc:trino:352://localhost/memory/default` diff --git a/docs/modules/databases/timeplus.md b/docs/modules/databases/timeplus.md new file mode 100644 index 00000000000..4f5cd4106a3 --- /dev/null +++ b/docs/modules/databases/timeplus.md @@ -0,0 +1,24 @@ +# Timeplus Module + +## Adding this module to your project dependencies + +Add the following dependency to your `pom.xml`/`build.gradle` file: + +=== "Gradle" + ```groovy + testImplementation "org.testcontainers:timeplus:{{latest_version}}" + ``` + +=== "Maven" + ```xml + + org.testcontainers + timeplus + {{latest_version}} + test + + ``` + +!!! hint + Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency. + diff --git a/mkdocs.yml b/mkdocs.yml index 8aed24d20f6..2e1c60c7dcc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -72,6 +72,7 @@ nav: - modules/databases/presto.md - modules/databases/questdb.md - modules/databases/tidb.md + - modules/databases/timeplus.md - modules/databases/trino.md - modules/databases/yugabytedb.md - modules/activemq.md diff --git a/modules/timeplus/build.gradle b/modules/timeplus/build.gradle new file mode 100644 index 00000000000..74570d821c2 --- /dev/null +++ b/modules/timeplus/build.gradle @@ -0,0 +1,10 @@ +description = "Testcontainers :: JDBC :: Timeplus" + +dependencies { + api project(':testcontainers') + api project(':jdbc') + + testImplementation project(':jdbc-test') + testRuntimeOnly 'com.timeplus:timeplus-native-jdbc:2.0.4' + testImplementation 'org.assertj:assertj-core:3.26.3' +} diff --git a/modules/timeplus/src/main/java/org/testcontainers/timeplus/TimeplusContainer.java b/modules/timeplus/src/main/java/org/testcontainers/timeplus/TimeplusContainer.java new file mode 100644 index 00000000000..0ba28c8f10a --- /dev/null +++ b/modules/timeplus/src/main/java/org/testcontainers/timeplus/TimeplusContainer.java @@ -0,0 +1,125 @@ +package org.testcontainers.timeplus; + +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +import java.time.Duration; +import java.util.HashSet; +import java.util.Set; + +/** + * Testcontainers implementation for Timeplus. + *

+ * Supported image: {@code timeplus/timeplusd} + *

+ * Exposed ports: + *

+ */ +public class TimeplusContainer extends JdbcDatabaseContainer { + + static final String NAME = "timeplus"; + + static final String DOCKER_IMAGE_NAME = "timeplus/timeplusd"; + + private static final DockerImageName TIMEPLUS_IMAGE_NAME = DockerImageName.parse(DOCKER_IMAGE_NAME); + + private static final Integer HTTP_PORT = 3218; + + private static final Integer NATIVE_PORT = 8463; + + private static final String DRIVER_CLASS_NAME = "com.timeplus.jdbc.TimeplusDriver"; + + private static final String JDBC_URL_PREFIX = "jdbc:" + NAME + "://"; + + private static final String TEST_QUERY = "SELECT 1"; + + private String databaseName = "default"; + + private String username = "default"; + + private String password = ""; + + public TimeplusContainer(String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public TimeplusContainer(final DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(TIMEPLUS_IMAGE_NAME); + + addExposedPorts(HTTP_PORT, NATIVE_PORT); + waitingFor(Wait.forHttp("/timeplusd/v1/ping").forStatusCode(200).withStartupTimeout(Duration.ofMinutes(1))); + } + + @Override + protected void configure() { + withEnv("TIMEPLUS_DB", this.databaseName); + withEnv("TIMEPLUS_USER", this.username); + withEnv("TIMEPLUS_PASSWORD", this.password); + } + + @Override + public Set getLivenessCheckPortNumbers() { + return new HashSet<>(getMappedPort(HTTP_PORT)); + } + + @Override + public String getDriverClassName() { + return DRIVER_CLASS_NAME; + } + + @Override + public String getJdbcUrl() { + return ( + JDBC_URL_PREFIX + + getHost() + + ":" + + getMappedPort(NATIVE_PORT) + + "/" + + this.databaseName + + constructUrlParameters("?", "&") + ); + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public String getPassword() { + return this.password; + } + + @Override + public String getDatabaseName() { + return this.databaseName; + } + + @Override + public String getTestQueryString() { + return TEST_QUERY; + } + + @Override + public TimeplusContainer withUsername(String username) { + this.username = username; + return this; + } + + @Override + public TimeplusContainer withPassword(String password) { + this.password = password; + return this; + } + + @Override + public TimeplusContainer withDatabaseName(String databaseName) { + this.databaseName = databaseName; + return this; + } +} diff --git a/modules/timeplus/src/main/java/org/testcontainers/timeplus/TimeplusContainerProvider.java b/modules/timeplus/src/main/java/org/testcontainers/timeplus/TimeplusContainerProvider.java new file mode 100644 index 00000000000..1d71e654a45 --- /dev/null +++ b/modules/timeplus/src/main/java/org/testcontainers/timeplus/TimeplusContainerProvider.java @@ -0,0 +1,32 @@ +package org.testcontainers.timeplus; + +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.JdbcDatabaseContainerProvider; +import org.testcontainers.utility.DockerImageName; + +/** + * Factory for Timeplus containers. + */ +public class TimeplusContainerProvider extends JdbcDatabaseContainerProvider { + + private static final String DEFAULT_TAG = "2.3.21"; + + @Override + public boolean supports(String databaseType) { + return databaseType.equals(TimeplusContainer.NAME); + } + + @Override + public JdbcDatabaseContainer newInstance() { + return newInstance(DEFAULT_TAG); + } + + @Override + public JdbcDatabaseContainer newInstance(String tag) { + if (tag != null) { + return new TimeplusContainer(DockerImageName.parse(TimeplusContainer.DOCKER_IMAGE_NAME).withTag(tag)); + } else { + return newInstance(); + } + } +} diff --git a/modules/timeplus/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider b/modules/timeplus/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider new file mode 100644 index 00000000000..f3122d3d73b --- /dev/null +++ b/modules/timeplus/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider @@ -0,0 +1 @@ +org.testcontainers.timeplus.TimeplusContainerProvider diff --git a/modules/timeplus/src/test/java/org/testcontainers/TimeplusImages.java b/modules/timeplus/src/test/java/org/testcontainers/TimeplusImages.java new file mode 100644 index 00000000000..4079e0b4524 --- /dev/null +++ b/modules/timeplus/src/test/java/org/testcontainers/TimeplusImages.java @@ -0,0 +1,7 @@ +package org.testcontainers; + +import org.testcontainers.utility.DockerImageName; + +public interface TimeplusImages { + DockerImageName TIMEPLUS_IMAGE = DockerImageName.parse("timeplus/timeplusd:2.3.21"); +} diff --git a/modules/timeplus/src/test/java/org/testcontainers/junit/timeplus/TimeplusJDBCDriverTest.java b/modules/timeplus/src/test/java/org/testcontainers/junit/timeplus/TimeplusJDBCDriverTest.java new file mode 100644 index 00000000000..3f3a7ac8136 --- /dev/null +++ b/modules/timeplus/src/test/java/org/testcontainers/junit/timeplus/TimeplusJDBCDriverTest.java @@ -0,0 +1,19 @@ +package org.testcontainers.junit.timeplus; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.testcontainers.jdbc.AbstractJDBCDriverTest; + +import java.util.Arrays; +import java.util.EnumSet; + +@RunWith(Parameterized.class) +public class TimeplusJDBCDriverTest extends AbstractJDBCDriverTest { + + @Parameterized.Parameters(name = "{index} - {0}") + public static Iterable data() { + return Arrays.asList( + new Object[][] { { "jdbc:tc:timeplus:2.3.21://hostname", EnumSet.noneOf(Options.class) } } + ); + } +} diff --git a/modules/timeplus/src/test/java/org/testcontainers/timeplus/TimeplusContainerTest.java b/modules/timeplus/src/test/java/org/testcontainers/timeplus/TimeplusContainerTest.java new file mode 100644 index 00000000000..95ecc1c6ef7 --- /dev/null +++ b/modules/timeplus/src/test/java/org/testcontainers/timeplus/TimeplusContainerTest.java @@ -0,0 +1,46 @@ +package org.testcontainers.timeplus; + +import org.junit.Test; +import org.testcontainers.TimeplusImages; +import org.testcontainers.db.AbstractContainerDatabaseTest; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TimeplusContainerTest extends AbstractContainerDatabaseTest { + + @Test + public void testSimple() throws SQLException { + try (TimeplusContainer timeplus = new TimeplusContainer(TimeplusImages.TIMEPLUS_IMAGE)) { + timeplus.start(); + + ResultSet resultSet = performQuery(timeplus, "SELECT 1"); + + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).isEqualTo(1); + } + } + + @Test + public void customCredentialsWithUrlParams() throws SQLException { + try ( + TimeplusContainer timeplus = new TimeplusContainer(TimeplusImages.TIMEPLUS_IMAGE) + .withUsername("system") + .withPassword("sys@t+") + .withDatabaseName("system") + .withUrlParam("interactive_delay", "5") + ) { + timeplus.start(); + + ResultSet resultSet = performQuery( + timeplus, + "SELECT to_int(value) FROM system.settings where name='interactive_delay'" + ); + + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).isEqualTo(5); + } + } +} diff --git a/modules/timeplus/src/test/resources/logback-test.xml b/modules/timeplus/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..83ef7a1a3ef --- /dev/null +++ b/modules/timeplus/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + +