diff --git a/modules/gaussdb/build.gradle b/modules/gaussdb/build.gradle
new file mode 100644
index 00000000000..5f932ebff06
--- /dev/null
+++ b/modules/gaussdb/build.gradle
@@ -0,0 +1,16 @@
+description = "Testcontainers :: JDBC :: GaussDB"
+
+dependencies {
+ api project(':jdbc')
+
+ compileOnly project(':r2dbc')
+// compileOnly 'io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE'
+
+ testImplementation project(':jdbc-test')
+ testRuntimeOnly 'com.huaweicloud.gaussdb:gaussdbjdbc:506.0.0.b058'
+
+// testImplementation testFixtures(project(':r2dbc'))
+// testRuntimeOnly 'io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE'
+
+ compileOnly 'org.jetbrains:annotations:24.1.0'
+}
diff --git a/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java
new file mode 100644
index 00000000000..77121d35cb0
--- /dev/null
+++ b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java
@@ -0,0 +1,152 @@
+package org.testcontainers.containers;
+
+import org.jetbrains.annotations.NotNull;
+import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
+import org.testcontainers.utility.DockerImageName;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.Set;
+
+/**
+ * Testcontainers implementation for PostgreSQL.
+ *
+ * Supported images: {@code postgres}, {@code pgvector/pgvector}
+ *
+ * Exposed ports: 5432
+ */
+public class GaussDBContainer> extends JdbcDatabaseContainer {
+
+ public static final String NAME = "gaussdb";
+
+ public static final String IMAGE = "opengauss/opengauss";
+
+ public static final String DEFAULT_TAG = "latest";
+
+ private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("opengauss/opengauss").asCompatibleSubstituteFor("gaussdb");
+
+ public static final Integer GaussDB_PORT = 5432;
+
+ static final String DEFAULT_USER = "gaussdb";
+
+ static final String DEFAULT_PASSWORD = "Enmo@123";
+
+ private String databaseName = "postgres";
+
+ private String username = "gaussdb";
+
+ private String password = "Enmo@123";
+
+ private static final String FSYNC_OFF_OPTION = "fsync=off";
+
+ /**
+ * @deprecated use {@link #GaussDBContainer(DockerImageName)} or {@link #GaussDBContainer(String)} instead
+ */
+ @Deprecated
+ public GaussDBContainer() {
+ this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));
+ }
+
+ public GaussDBContainer(final String dockerImageName) {
+ this(DockerImageName.parse(dockerImageName));
+ }
+
+ public GaussDBContainer(final DockerImageName dockerImageName) {
+ super(dockerImageName);
+ dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
+ this.withEnv("GS_PASSWORD", "Enmo@123")
+ .withDatabaseName("postgres");
+ // Comment out the test error code for the time being
+// this.waitStrategy
+// =
+// new LogMessageWaitStrategy()
+// .withRegEx(".*can not read GAUSS_WARNING_TYPE env.*\\s")
+// .withTimes(1)
+// .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS));
+// this.setCommand("-c", FSYNC_OFF_OPTION);
+
+ addExposedPort(GaussDB_PORT);
+ }
+
+ /**
+ * @return the ports on which to check if the container is ready
+ * @deprecated use {@link #getLivenessCheckPortNumbers()} instead
+ */
+ @NotNull
+ @Override
+ @Deprecated
+ protected Set getLivenessCheckPorts() {
+ return super.getLivenessCheckPorts();
+ }
+
+ @Override
+ protected void configure() {
+ // Disable Postgres driver use of java.util.logging to reduce noise at startup time
+ withUrlParam("loggerLevel", "OFF");
+ addEnv("POSTGRES_DB", databaseName);
+ addEnv("POSTGRES_USER", username);
+ addEnv("POSTGRES_PASSWORD", password);
+ }
+
+ @Override
+ public String getDriverClassName() {
+ return "com.huawei.gaussdb.jdbc.Driver";
+ }
+
+ @Override
+ public String getJdbcUrl() {
+ String additionalUrlParams = constructUrlParameters("?", "&");
+ return (
+ "jdbc:gaussdb://" +
+ getHost() +
+ ":" +
+ getMappedPort(GaussDB_PORT) +
+ "/" +
+ databaseName +
+ additionalUrlParams
+ );
+ }
+
+ @Override
+ public String getDatabaseName() {
+ return databaseName;
+ }
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+
+ @Override
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public String getTestQueryString() {
+ return "SELECT 1";
+ }
+
+ @Override
+ public SELF withDatabaseName(final String databaseName) {
+ this.databaseName = databaseName;
+ return self();
+ }
+
+ @Override
+ public SELF withUsername(final String username) {
+ this.username = username;
+ return self();
+ }
+
+ @Override
+ public SELF withPassword(final String password) {
+ this.password = password;
+ return self();
+ }
+
+ @Override
+ protected void waitUntilContainerStarted() {
+ getWaitStrategy().waitUntilReady(this);
+ }
+}
diff --git a/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainerProvider.java b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainerProvider.java
new file mode 100644
index 00000000000..e21aebb85ec
--- /dev/null
+++ b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainerProvider.java
@@ -0,0 +1,34 @@
+package org.testcontainers.containers;
+
+import org.testcontainers.jdbc.ConnectionUrl;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * Factory for GaussDB containers.
+ */
+public class GaussDBContainerProvider extends JdbcDatabaseContainerProvider {
+
+ public static final String USER_PARAM = "user";
+
+ public static final String PASSWORD_PARAM = "password";
+
+ @Override
+ public boolean supports(String databaseType) {
+ return databaseType.equals(GaussDBContainer.NAME);
+ }
+
+ @Override
+ public JdbcDatabaseContainer newInstance() {
+ return newInstance(GaussDBContainer.DEFAULT_TAG);
+ }
+
+ @Override
+ public JdbcDatabaseContainer newInstance(String tag) {
+ return new GaussDBContainer(DockerImageName.parse(GaussDBContainer.IMAGE).withTag(tag));
+ }
+
+ @Override
+ public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) {
+ return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM);
+ }
+}
diff --git a/modules/gaussdb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider b/modules/gaussdb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider
new file mode 100644
index 00000000000..9707299c2ba
--- /dev/null
+++ b/modules/gaussdb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider
@@ -0,0 +1 @@
+org.testcontainers.containers.GaussDBContainerProvider
diff --git a/modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java b/modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java
new file mode 100644
index 00000000000..fbc1dbd5e62
--- /dev/null
+++ b/modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java
@@ -0,0 +1,7 @@
+package org.testcontainers;
+
+import org.testcontainers.utility.DockerImageName;
+
+public interface GaussDBTestImages {
+ DockerImageName GAUSSDB_TEST_IMAGE = DockerImageName.parse("opengauss/opengauss:latest").asCompatibleSubstituteFor("gaussdb");
+}
diff --git a/modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java b/modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java
new file mode 100644
index 00000000000..9db9b3fe38e
--- /dev/null
+++ b/modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java
@@ -0,0 +1,84 @@
+package org.testcontainers.containers;
+
+import org.junit.Test;
+import org.testcontainers.GaussDBTestImages;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+
+public class GaussDBConnectionURLTest {
+
+ @Test
+ public void shouldCorrectlyAppendQueryString() {
+ GaussDBContainer> postgres = new FixedJdbcUrlPostgreSQLContainer();
+ String connectionUrl = postgres.constructUrlForConnection("?stringtype=unspecified&stringtype=unspecified");
+ String queryString = connectionUrl.substring(connectionUrl.indexOf('?'));
+
+ assertThat(queryString)
+ .as("Query String contains expected params")
+ .contains("?stringtype=unspecified&stringtype=unspecified");
+ assertThat(queryString.indexOf('?')).as("Query String starts with '?'").isZero();
+ assertThat(queryString.substring(1)).as("Query String does not contain extra '?'").doesNotContain("?");
+ }
+
+ @Test
+ public void shouldCorrectlyAppendQueryStringWhenNoBaseParams() {
+ GaussDBContainer> postgres = new NoParamsUrlPostgreSQLContainer();
+ String connectionUrl = postgres.constructUrlForConnection("?stringtype=unspecified&stringtype=unspecified");
+ String queryString = connectionUrl.substring(connectionUrl.indexOf('?'));
+
+ assertThat(queryString)
+ .as("Query String contains expected params")
+ .contains("?stringtype=unspecified&stringtype=unspecified");
+ assertThat(queryString.indexOf('?')).as("Query String starts with '?'").isZero();
+ assertThat(queryString.substring(1)).as("Query String does not contain extra '?'").doesNotContain("?");
+ }
+
+ @Test
+ public void shouldReturnOriginalURLWhenEmptyQueryString() {
+ GaussDBContainer> postgres = new FixedJdbcUrlPostgreSQLContainer();
+ String connectionUrl = postgres.constructUrlForConnection("");
+
+ assertThat(postgres.getJdbcUrl()).as("Query String remains unchanged").isEqualTo(connectionUrl);
+ }
+
+ @Test
+ public void shouldRejectInvalidQueryString() {
+ assertThat(
+ catchThrowable(() -> {
+ new NoParamsUrlPostgreSQLContainer().constructUrlForConnection("stringtype=unspecified");
+ })
+ )
+ .as("Fails when invalid query string provided")
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ static class FixedJdbcUrlPostgreSQLContainer extends GaussDBContainer {
+
+ public FixedJdbcUrlPostgreSQLContainer() {
+ super(GaussDBTestImages.GAUSSDB_TEST_IMAGE);
+ }
+
+ @Override
+ public String getHost() {
+ return "localhost";
+ }
+
+ @Override
+ public Integer getMappedPort(int originalPort) {
+ return 34532;
+ }
+ }
+
+ static class NoParamsUrlPostgreSQLContainer extends GaussDBContainer {
+
+ public NoParamsUrlPostgreSQLContainer() {
+ super(GaussDBTestImages.GAUSSDB_TEST_IMAGE);
+ }
+
+ @Override
+ public String getJdbcUrl() {
+ return "jdbc:postgresql://host:port/database";
+ }
+ }
+}
diff --git a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java
new file mode 100644
index 00000000000..803f0795a3e
--- /dev/null
+++ b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java
@@ -0,0 +1,52 @@
+package org.testcontainers.jdbc;
+
+import org.junit.AfterClass;
+import org.junit.Test;
+import org.testcontainers.containers.JdbcDatabaseContainer;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * This test belongs in the jdbc module, as it is focused on testing the behaviour of {@link org.testcontainers.containers.JdbcDatabaseContainer}.
+ * However, the need to use the {@link org.testcontainers.containers.GaussDBContainerProvider} (due to the jdbc:tc:postgresql) URL forces it to live here in
+ * the mysql module, to avoid circular dependencies.
+ * TODO: Move to the jdbc module and either (a) implement a barebones {@link org.testcontainers.containers.JdbcDatabaseContainerProvider} for testing, or (b) refactor into a unit test.
+ */
+public class DatabaseDriverShutdownTest {
+
+ @AfterClass
+ public static void testCleanup() {
+ ContainerDatabaseDriver.killContainers();
+ }
+
+ @Test
+ public void shouldStopContainerWhenAllConnectionsClosed() throws SQLException {
+ final String jdbcUrl = "jdbc:tc:gaussdb://hostname/databasename";
+
+ getConnectionAndClose(jdbcUrl);
+
+ JdbcDatabaseContainer> container = ContainerDatabaseDriver.getContainer(jdbcUrl);
+ assertThat(container).as("Database container instance is null as expected").isNull();
+ }
+
+ @Test
+ public void shouldNotStopDaemonContainerWhenAllConnectionsClosed() throws SQLException {
+ final String jdbcUrl = "jdbc:tc:gaussdb://hostname/databasename?TC_DAEMON=true";
+
+ getConnectionAndClose(jdbcUrl);
+
+ JdbcDatabaseContainer> container = ContainerDatabaseDriver.getContainer(jdbcUrl);
+ assertThat(container).as("Database container instance is not null as expected").isNotNull();
+ assertThat(container.isRunning()).as("Database container is running as expected").isTrue();
+ }
+
+ private void getConnectionAndClose(String jdbcUrl) throws SQLException {
+ try (Connection connection = DriverManager.getConnection(jdbcUrl)) {
+ assertThat(connection).as("Obtained connection as expected").isNotNull();
+ }
+ }
+}
diff --git a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java
new file mode 100644
index 00000000000..48d2d43386c
--- /dev/null
+++ b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java
@@ -0,0 +1,37 @@
+package org.testcontainers.jdbc;
+
+import org.junit.Test;
+import org.testcontainers.containers.Container;
+import org.testcontainers.containers.JdbcDatabaseContainer;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * This test belongs in the jdbc module, as it is focused on testing the behaviour of {@link org.testcontainers.containers.JdbcDatabaseContainer}.
+ * However, the need to use the {@link org.testcontainers.containers.GaussDBContainerProvider} (due to the jdbc:tc:postgresql) URL forces it to live here in
+ * the mysql module, to avoid circular dependencies.
+ * TODO: Move to the jdbc module and either (a) implement a barebones {@link org.testcontainers.containers.JdbcDatabaseContainerProvider} for testing, or (b) refactor into a unit test.
+ */
+public class DatabaseDriverTmpfsTest {
+
+ @Test
+ public void testDatabaseHasTmpFsViaConnectionString() throws Exception {
+ final String jdbcUrl = "jdbc:tc:gaussdb://hostname/databasename?TC_TMPFS=/testtmpfs:rw";
+ try (Connection ignored = DriverManager.getConnection(jdbcUrl)) {
+ JdbcDatabaseContainer> container = ContainerDatabaseDriver.getContainer(jdbcUrl);
+ // check file doesn't exist
+ String path = "/testtmpfs/test.file";
+ Container.ExecResult execResult = container.execInContainer("ls", path);
+ assertThat(execResult.getExitCode())
+ .as("tmpfs inside container doesn't have file that doesn't exist")
+ .isNotZero();
+ // touch && check file does exist
+ container.execInContainer("touch", path);
+ execResult = container.execInContainer("ls", path);
+ assertThat(execResult.getExitCode()).as("tmpfs inside container has file that does exist").isZero();
+ }
+ }
+}
diff --git a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java
new file mode 100644
index 00000000000..7fc88226844
--- /dev/null
+++ b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java
@@ -0,0 +1,24 @@
+package org.testcontainers.jdbc.gaussdb;
+
+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 GaussDBJDBCDriverTest extends AbstractJDBCDriverTest {
+
+ @Parameterized.Parameters(name = "{index} - {0}")
+ public static Iterable