diff --git a/docs/modules/databases/jdbc.md b/docs/modules/databases/jdbc.md index 7adaf2f5e70..d402b5983b4 100644 --- a/docs/modules/databases/jdbc.md +++ b/docs/modules/databases/jdbc.md @@ -39,6 +39,10 @@ Insert `tc:` after `jdbc:` as follows. Note that the hostname, port and database `jdbc:tc:postgis:9.6-2.5:///databasename` +#### Using TimescaleDB + +`jdbc:tc:timescaldb:2.1.0-pg13:///databasename` + #### Using Trino `jdbc:tc:trino:352://localhost/memory/default` diff --git a/modules/jdbc-test/src/main/java/org/testcontainers/jdbc/AbstractJDBCDriverTest.java b/modules/jdbc-test/src/main/java/org/testcontainers/jdbc/AbstractJDBCDriverTest.java index 38ca2237567..580aad513e3 100644 --- a/modules/jdbc-test/src/main/java/org/testcontainers/jdbc/AbstractJDBCDriverTest.java +++ b/modules/jdbc-test/src/main/java/org/testcontainers/jdbc/AbstractJDBCDriverTest.java @@ -117,7 +117,9 @@ private void performTestForJDBCParamUsage(HikariDataSource dataSource) throws SQ String databaseQuery = "SELECT DATABASE()"; // Postgres does not have Database() as a function String databaseType = ConnectionUrl.newInstance(jdbcUrl).getDatabaseType(); - if (databaseType.equalsIgnoreCase("postgresql") || databaseType.equalsIgnoreCase("postgis")) { + if (databaseType.equalsIgnoreCase("postgresql") || + databaseType.equalsIgnoreCase("postgis") || + databaseType.equalsIgnoreCase("timescaledb")) { databaseQuery = "SELECT CURRENT_DATABASE()"; } diff --git a/modules/postgresql/src/main/java/org/testcontainers/containers/TimescaleDBContainerProvider.java b/modules/postgresql/src/main/java/org/testcontainers/containers/TimescaleDBContainerProvider.java new file mode 100644 index 00000000000..c9b81fccfe4 --- /dev/null +++ b/modules/postgresql/src/main/java/org/testcontainers/containers/TimescaleDBContainerProvider.java @@ -0,0 +1,39 @@ +package org.testcontainers.containers; + +import org.testcontainers.jdbc.ConnectionUrl; +import org.testcontainers.utility.DockerImageName; + +/** + * Factory for TimescaleDB containers, which are a special flavour of PostgreSQL. + * + * @see https://docs.timescale.com/latest/introduction + */ +public class TimescaleDBContainerProvider extends JdbcDatabaseContainerProvider { + + private static final String NAME = "timescaledb"; + private static final String DEFAULT_TAG = "2.1.0-pg11"; + private static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse("timescale/timescaledb").asCompatibleSubstituteFor("postgres"); + public static final String USER_PARAM = "user"; + public static final String PASSWORD_PARAM = "password"; + + + @Override + public boolean supports(String databaseType) { + return databaseType.equals(NAME); + } + + @Override + public JdbcDatabaseContainer newInstance() { + return newInstance(DEFAULT_TAG); + } + + @Override + public JdbcDatabaseContainer newInstance(String tag) { + return new PostgreSQLContainer(DEFAULT_IMAGE.withTag(tag)); + } + + @Override + public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) { + return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM); + } +} diff --git a/modules/postgresql/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider b/modules/postgresql/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider index 79b2f29340a..05df9054522 100644 --- a/modules/postgresql/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider +++ b/modules/postgresql/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider @@ -1,2 +1,3 @@ org.testcontainers.containers.PostgreSQLContainerProvider org.testcontainers.containers.PostgisContainerProvider +org.testcontainers.containers.TimescaleDBContainerProvider diff --git a/modules/postgresql/src/test/java/org/testcontainers/containers/TimescaleDBContainerTest.java b/modules/postgresql/src/test/java/org/testcontainers/containers/TimescaleDBContainerTest.java new file mode 100644 index 00000000000..f3c73a4111b --- /dev/null +++ b/modules/postgresql/src/test/java/org/testcontainers/containers/TimescaleDBContainerTest.java @@ -0,0 +1,58 @@ +package org.testcontainers.containers; + +import org.junit.Test; +import org.testcontainers.db.AbstractContainerDatabaseTest; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; +import static org.rnorth.visibleassertions.VisibleAssertions.assertNotEquals; + +public class TimescaleDBContainerTest extends AbstractContainerDatabaseTest { + + @Test + public void testSimple() throws SQLException { + try (JdbcDatabaseContainer postgres = new TimescaleDBContainerProvider().newInstance()) { + postgres.start(); + + ResultSet resultSet = performQuery(postgres, "SELECT 1"); + int resultSetInt = resultSet.getInt(1); + assertEquals("A basic SELECT query succeeds", 1, resultSetInt); + } + } + + @Test + public void testCommandOverride() throws SQLException { + try (GenericContainer postgres = new TimescaleDBContainerProvider().newInstance().withCommand("postgres -c max_connections=42")) { + postgres.start(); + + ResultSet resultSet = performQuery((JdbcDatabaseContainer) postgres, "SELECT current_setting('max_connections')"); + String result = resultSet.getString(1); + assertEquals("max_connections should be overriden", "42", result); + } + } + + @Test + public void testUnsetCommand() throws SQLException { + try (GenericContainer postgres = new TimescaleDBContainerProvider().newInstance().withCommand("postgres -c max_connections=42").withCommand()) { + postgres.start(); + + ResultSet resultSet = performQuery((JdbcDatabaseContainer) postgres, "SELECT current_setting('max_connections')"); + String result = resultSet.getString(1); + assertNotEquals("max_connections should not be overriden", "42", result); + } + } + + @Test + public void testExplicitInitScript() throws SQLException { + try (JdbcDatabaseContainer postgres = new TimescaleDBContainerProvider().newInstance().withInitScript("somepath/init_timescaledb.sql")) { + postgres.start(); + + ResultSet resultSet = performQuery(postgres, "SELECT foo FROM bar"); + + String firstColumnValue = resultSet.getString(1); + assertEquals("Value from init script should equal real value", "hello world", firstColumnValue); + } + } +} diff --git a/modules/postgresql/src/test/java/org/testcontainers/jdbc/timescaledb/TimescaleDBJDBCDriverTest.java b/modules/postgresql/src/test/java/org/testcontainers/jdbc/timescaledb/TimescaleDBJDBCDriverTest.java new file mode 100644 index 00000000000..8a6eaa3d04b --- /dev/null +++ b/modules/postgresql/src/test/java/org/testcontainers/jdbc/timescaledb/TimescaleDBJDBCDriverTest.java @@ -0,0 +1,22 @@ +package org.testcontainers.jdbc.timescaledb; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.testcontainers.jdbc.AbstractJDBCDriverTest; + +import java.util.EnumSet; + +import static java.util.Arrays.asList; + +@RunWith(Parameterized.class) +public class TimescaleDBJDBCDriverTest extends AbstractJDBCDriverTest { + + @Parameterized.Parameters(name = "{index} - {0}") + public static Iterable data() { + return asList( + new Object[][]{ + {"jdbc:tc:timescaledb://hostname/databasename?user=someuser&password=somepwd", EnumSet.of(Options.JDBCParams)}, + {"jdbc:tc:timescaledb:2.1.0-pg13://hostname/databasename?user=someuser&password=somepwd", EnumSet.of(Options.JDBCParams)}, + }); + } +} diff --git a/modules/postgresql/src/test/resources/somepath/init_timescaledb.sql b/modules/postgresql/src/test/resources/somepath/init_timescaledb.sql new file mode 100644 index 00000000000..da11604cf10 --- /dev/null +++ b/modules/postgresql/src/test/resources/somepath/init_timescaledb.sql @@ -0,0 +1,13 @@ +-- Extend the database with TimescaleDB +CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE; + +CREATE TABLE bar +( + foo VARCHAR(255), + time TIMESTAMPTZ NOT NULL +); + +SELECT create_hypertable('bar', 'time'); + +INSERT INTO bar (time, foo) +VALUES (CURRENT_TIMESTAMP, 'hello world');