From 61ce025d2affb602384af6312235f895bb104983 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Sat, 8 Feb 2020 21:07:45 +0100 Subject: [PATCH] Add Presto container (#2196) * Add Presto container * Add incubating note to module docs Co-authored-by: Richard North --- docs/modules/databases/index.md | 4 + docs/modules/databases/presto.md | 88 +++++++++++++ mkdocs.yml | 1 + modules/jdbc-test/build.gradle | 2 + .../testcontainers/jdbc/JDBCDriverTest.java | 1 + modules/presto/build.gradle | 8 ++ .../containers/PrestoContainer.java | 104 +++++++++++++++ .../containers/PrestoContainerProvider.java | 35 +++++ ...s.containers.JdbcDatabaseContainerProvider | 1 + .../containers/PrestoContainerTest.java | 121 ++++++++++++++++++ modules/presto/src/test/resources/initial.sql | 2 + 11 files changed, 367 insertions(+) create mode 100644 docs/modules/databases/presto.md create mode 100644 modules/presto/build.gradle create mode 100644 modules/presto/src/main/java/org/testcontainers/containers/PrestoContainer.java create mode 100644 modules/presto/src/main/java/org/testcontainers/containers/PrestoContainerProvider.java create mode 100644 modules/presto/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider create mode 100644 modules/presto/src/test/java/org/testcontainers/containers/PrestoContainerTest.java create mode 100644 modules/presto/src/test/resources/initial.sql diff --git a/docs/modules/databases/index.md b/docs/modules/databases/index.md index 32026953802..d7654cc6a33 100644 --- a/docs/modules/databases/index.md +++ b/docs/modules/databases/index.md @@ -69,6 +69,10 @@ Insert `tc:` after `jdbc:` as follows. Note that the hostname, port and database `jdbc:tc:postgis:9.6:///databasename` +### Using Presto + +`jdbc:tc:presto:329://localhost/memory/default` + ## Using a classpath init script Testcontainers can run an init script after the database container is started, but before your code is given a connection to it. The script must be on the classpath, and is referenced as follows: diff --git a/docs/modules/databases/presto.md b/docs/modules/databases/presto.md new file mode 100644 index 00000000000..52776d06daf --- /dev/null +++ b/docs/modules/databases/presto.md @@ -0,0 +1,88 @@ +# Presto Module + +!!! note + This module is INCUBATING. While it is ready for use and operational in the current version of Testcontainers, it is possible that it may receive breaking changes in the future. See [our contributing guidelines](/contributing/#incubating-modules) for more information on our incubating modules policy. + +See [Database containers](./index.md) for documentation and usage that is common to all database container types. + +## Usage example + +Running Presto as a stand-in for in a test: + +```java +public class SomeTest { + + @Rule + public PrestoContainer presto = new PrestoContainer(); + + @Test + public void someTestMethod() { + String url = presto.getJdbcUrl(); + + ... create a connection and run test as normal +``` + +Presto comes with several catalogs preconfigured. Most useful ones for testing are + +* `tpch` catalog using the [Presto TPCH Connector](https://prestosql.io/docs/current/connector/tpch.html). + This is a read-only catalog that defines standard TPCH schema, so is available for querying without a need + to create any tables. +* `memory` catalog using the [Presto Memory Connector](https://prestosql.io/docs/current/connector/memory.html). + This catalog can be used for creating schemas and tables and does not require any storage, as everything + is stored fully in-memory. + +Example test using the `tpch` and `memory` catalogs: + +```java +public class SomeTest { + @Rule + public PrestoContainer prestoSql = new PrestoContainer(); + + @Test + public void queryMemoryAndTpchConnectors() throws SQLException { + try (Connection connection = prestoSql.createConnection(); + Statement statement = connection.createStatement()) { + // Prepare data + statement.execute("CREATE TABLE memory.default.table_with_array AS SELECT 1 id, ARRAY[1, 42, 2, 42, 4, 42] my_array"); + + // Query Presto using newly created table and a builtin connector + try (ResultSet resultSet = statement.executeQuery("" + + "SELECT nationkey, element " + + "FROM tpch.tiny.nation " + + "JOIN memory.default.table_with_array twa ON nationkey = twa.id " + + "LEFT JOIN UNNEST(my_array) a(element) ON true " + + "ORDER BY element OFFSET 1 FETCH NEXT 3 ROWS WITH TIES ")) { + List actualElements = new ArrayList<>(); + while (resultSet.next()) { + actualElements.add(resultSet.getInt("element")); + } + Assert.assertEquals(Arrays.asList(2, 4, 42, 42, 42), actualElements); + } + } + } +} +``` + +## Adding this module to your project dependencies + +Add the following dependency to your `pom.xml`/`build.gradle` file: + +```groovy tab='Gradle' +testCompile "org.testcontainers:presto:{{latest_version}}" +``` + +```xml tab='Maven' + + org.testcontainers + presto + {{latest_version}} + test + +``` + +!!! hint + Adding this Testcontainers library JAR will not automatically add the Presto JDBC driver JAR to your project. + You should ensure that your project has the Presto JDBC driver as a dependency, if you plan on using it. + Refer to [Presto project download page](https://prestosql.io/download.html) for instructions. + + diff --git a/mkdocs.yml b/mkdocs.yml index 189bc3b6788..f66f382ccb0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -53,6 +53,7 @@ nav: - modules/databases/neo4j.md - modules/databases/oraclexe.md - modules/databases/postgres.md + - modules/databases/presto.md - modules/docker_compose.md - modules/elasticsearch.md - modules/kafka.md diff --git a/modules/jdbc-test/build.gradle b/modules/jdbc-test/build.gradle index 842f2a761eb..c7c909782bb 100644 --- a/modules/jdbc-test/build.gradle +++ b/modules/jdbc-test/build.gradle @@ -7,6 +7,7 @@ repositories { dependencies { compile project(':mysql') compile project(':postgresql') + compile project(':presto') compile project(':cockroachdb') compile project(':mariadb') compile project(':oracle-xe') @@ -16,6 +17,7 @@ dependencies { testCompile 'com.google.guava:guava:18.0' testCompile 'org.postgresql:postgresql:42.0.0' + testCompile 'io.prestosql:presto-jdbc:329' testCompile 'mysql:mysql-connector-java:8.0.14' testCompile 'org.mariadb.jdbc:mariadb-java-client:1.4.6' testCompile 'com.oracle:ojdbc6:12.1.0.1-atlassian-hosted' diff --git a/modules/jdbc-test/src/test/java/org/testcontainers/jdbc/JDBCDriverTest.java b/modules/jdbc-test/src/test/java/org/testcontainers/jdbc/JDBCDriverTest.java index 2089513df06..ca55a1d1841 100644 --- a/modules/jdbc-test/src/test/java/org/testcontainers/jdbc/JDBCDriverTest.java +++ b/modules/jdbc-test/src/test/java/org/testcontainers/jdbc/JDBCDriverTest.java @@ -53,6 +53,7 @@ public static Iterable data() { {"jdbc:tc:postgresql:9.6.8://hostname/databasename?user=someuser&password=somepwd", EnumSet.of(Options.JDBCParams)}, {"jdbc:tc:postgis://hostname/databasename?user=someuser&password=somepwd", EnumSet.of(Options.JDBCParams)}, {"jdbc:tc:postgis:9.6://hostname/databasename?user=someuser&password=somepwd", EnumSet.of(Options.JDBCParams)}, + {"jdbc:tc:presto:329://hostname/", EnumSet.of(Options.PmdKnownBroken)}, {"jdbc:tc:mysql:5.6://hostname/databasename?TC_MY_CNF=somepath/mysql_conf_override", EnumSet.of(Options.CustomIniFile)}, {"jdbc:tc:mariadb://hostname/databasename", EnumSet.noneOf(Options.class)}, {"jdbc:tc:mariadb://hostname/databasename?user=someuser&TC_INITSCRIPT=somepath/init_mariadb.sql", EnumSet.of(Options.ScriptedSchema, Options.JDBCParams)}, diff --git a/modules/presto/build.gradle b/modules/presto/build.gradle new file mode 100644 index 00000000000..07aad11168e --- /dev/null +++ b/modules/presto/build.gradle @@ -0,0 +1,8 @@ +description = "Testcontainers :: JDBC :: Presto" + +dependencies { + compile project(':jdbc') + + testCompile 'io.prestosql:presto-jdbc:329' + testCompile 'commons-dbutils:commons-dbutils:1.7' +} diff --git a/modules/presto/src/main/java/org/testcontainers/containers/PrestoContainer.java b/modules/presto/src/main/java/org/testcontainers/containers/PrestoContainer.java new file mode 100644 index 00000000000..186acb18af6 --- /dev/null +++ b/modules/presto/src/main/java/org/testcontainers/containers/PrestoContainer.java @@ -0,0 +1,104 @@ +package org.testcontainers.containers; + +import org.jetbrains.annotations.NotNull; +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; + +import java.sql.Connection; +import java.sql.SQLException; +import java.time.Duration; +import java.util.HashSet; +import java.util.Set; + +import static com.google.common.base.Strings.nullToEmpty; +import static java.lang.String.format; +import static java.time.temporal.ChronoUnit.SECONDS; + +public class PrestoContainer> extends JdbcDatabaseContainer { + public static final String NAME = "presto"; + public static final String IMAGE = "prestosql/presto"; + public static final String DEFAULT_TAG = "329"; + + public static final Integer PRESTO_PORT = 8080; + + private String username = "test"; + private String catalog = null; + + public PrestoContainer() { + this(IMAGE + ":" + DEFAULT_TAG); + } + + public PrestoContainer(final String dockerImageName) { + super(dockerImageName); + this.waitStrategy = new LogMessageWaitStrategy() + .withRegEx(".*io.prestosql.server.PrestoServer\\s+======== SERVER STARTED ========.*") + .withStartupTimeout(Duration.of(60, SECONDS)); + } + + @NotNull + @Override + protected Set getLivenessCheckPorts() { + return new HashSet<>(getMappedPort(PRESTO_PORT)); + } + + @Override + protected void configure() { + addExposedPort(PRESTO_PORT); + } + + @Override + public String getDriverClassName() { + return "io.prestosql.jdbc.PrestoDriver"; + } + + @Override + public String getJdbcUrl() { + return format("jdbc:presto://%s:%s/%s", getContainerIpAddress(), getMappedPort(PRESTO_PORT), nullToEmpty(catalog)); + } + + @Override + public String getUsername() { + return username; + } + + @Override + public String getPassword() { + return ""; + } + + @Override + public String getDatabaseName() { + return catalog; + } + + @Override + public String getTestQueryString() { + return "SELECT count(*) FROM tpch.tiny.nation"; + } + + @Override + public SELF withUsername(final String username) { + this.username = username; + return self(); + } + + /** + * @deprecated This operation is not supported. + */ + @Override + @Deprecated + public SELF withPassword(final String password) { + // ignored, Presto does not support password authentication without TLS + // TODO: make JDBCDriverTest not pass a password unconditionally and remove this method + return self(); + } + + @Override + public SELF withDatabaseName(String dbName) { + this.catalog = dbName; + return self(); + } + + public Connection createConnection() throws SQLException, NoDriverFoundException { + return createConnection(""); + } +} diff --git a/modules/presto/src/main/java/org/testcontainers/containers/PrestoContainerProvider.java b/modules/presto/src/main/java/org/testcontainers/containers/PrestoContainerProvider.java new file mode 100644 index 00000000000..663cf9be4a9 --- /dev/null +++ b/modules/presto/src/main/java/org/testcontainers/containers/PrestoContainerProvider.java @@ -0,0 +1,35 @@ +package org.testcontainers.containers; + +import org.testcontainers.jdbc.ConnectionUrl; + +import java.util.Objects; + +/** + * Factory for Presto containers. + */ +public class PrestoContainerProvider 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(PrestoContainer.NAME); + } + + @Override + public JdbcDatabaseContainer newInstance() { + return newInstance(PrestoContainer.DEFAULT_TAG); + } + + @Override + public JdbcDatabaseContainer newInstance(String tag) { + return new PrestoContainer(PrestoContainer.IMAGE + ":" + tag); + } + + @Override + public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) { + return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM); + } + +} diff --git a/modules/presto/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider b/modules/presto/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider new file mode 100644 index 00000000000..ad36a36a117 --- /dev/null +++ b/modules/presto/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider @@ -0,0 +1 @@ +org.testcontainers.containers.PrestoContainerProvider diff --git a/modules/presto/src/test/java/org/testcontainers/containers/PrestoContainerTest.java b/modules/presto/src/test/java/org/testcontainers/containers/PrestoContainerTest.java new file mode 100644 index 00000000000..07fb6fa30ed --- /dev/null +++ b/modules/presto/src/test/java/org/testcontainers/containers/PrestoContainerTest.java @@ -0,0 +1,121 @@ +package org.testcontainers.containers; + +import org.junit.Assert; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static java.lang.Integer.parseInt; +import static java.lang.String.format; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author findepi + */ +public class PrestoContainerTest { + @Test + public void testSimple() throws Exception { + try (PrestoContainer prestoSql = new PrestoContainer<>()) { + prestoSql.start(); + try (Connection connection = prestoSql.createConnection(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT DISTINCT node_version FROM system.runtime.nodes")) { + assertTrue("No result", resultSet.next()); + assertEquals("Presto version", PrestoContainer.DEFAULT_TAG, resultSet.getString("node_version")); + } + } + } + + @Test + public void testSpecificVersion() throws Exception { + String prestoVersion = Integer.toString(parseInt(PrestoContainer.DEFAULT_TAG) - 1); + try (PrestoContainer prestoSql = new PrestoContainer<>("prestosql/presto:" + prestoVersion)) { + prestoSql.start(); + try (Connection connection = prestoSql.createConnection(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT DISTINCT node_version FROM system.runtime.nodes")) { + assertTrue("No result", resultSet.next()); + assertEquals("Presto version", prestoVersion, resultSet.getString("node_version")); + } + } + } + + @Test + public void testQueryMemoryAndTpch() throws SQLException { + try (PrestoContainer prestoSql = new PrestoContainer<>()) { + prestoSql.start(); + try (Connection connection = prestoSql.createConnection(); + Statement statement = connection.createStatement()) { + // Prepare data + statement.execute("CREATE TABLE memory.default.table_with_array AS SELECT 1 id, ARRAY[1, 42, 2, 42, 4, 42] my_array"); + + // Query Presto using newly created table and a builtin connector + try (ResultSet resultSet = statement.executeQuery("" + + "SELECT nationkey, element " + + "FROM tpch.tiny.nation " + + "JOIN memory.default.table_with_array twa ON nationkey = twa.id " + + "LEFT JOIN UNNEST(my_array) a(element) ON true " + + "ORDER BY element OFFSET 1 FETCH NEXT 3 ROWS WITH TIES ")) { + List actualElements = new ArrayList<>(); + while (resultSet.next()) { + actualElements.add(resultSet.getInt("element")); + } + Assert.assertEquals(Arrays.asList(2, 4, 42, 42, 42), actualElements); + } + } + } + } + + @Test + public void testInitScript() throws Exception { + try (PrestoContainer prestoSql = new PrestoContainer<>()) { + prestoSql.withInitScript("initial.sql"); + prestoSql.start(); + try (Connection connection = prestoSql.createConnection(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT a FROM memory.default.test_table")) { + assertTrue("No result", resultSet.next()); + assertEquals("Value", 12345678909324L, resultSet.getObject("a")); + assertFalse("Too many result", resultSet.next()); + } + } + } + + @Test + public void testTcJdbcUri() throws Exception { + try (Connection connection = DriverManager.getConnection(format("jdbc:tc:presto:%s://hostname/", PrestoContainer.DEFAULT_TAG))) { + // Verify metadata with tc: JDBC connection URI + assertEquals(connection.getMetaData().getDatabaseMajorVersion(), parseInt(PrestoContainer.DEFAULT_TAG)); + + // Verify transactions with tc: JDBC connection URI + assertTrue("Is autocommit", connection.getAutoCommit()); + connection.setAutoCommit(false); + assertFalse("Is autocommit", connection.getAutoCommit()); + assertEquals("Transaction isolation", Connection.TRANSACTION_READ_UNCOMMITTED, connection.getTransactionIsolation()); + + try (Statement statement = connection.createStatement()) { + assertEquals("Update result", 0, statement.executeUpdate("CREATE TABLE memory.default.test_tc(a bigint)")); + try (ResultSet resultSet = statement.executeQuery("SELECT sum(cast(node_version AS bigint)) AS v FROM system.runtime.nodes")) { + assertTrue(resultSet.next()); + assertEquals(PrestoContainer.DEFAULT_TAG, resultSet.getString("v")); + assertFalse(resultSet.next()); + } + connection.commit(); + } finally { + connection.rollback(); + } + connection.setAutoCommit(true); + assertTrue("Is autocommit", connection.getAutoCommit()); + assertEquals("Transaction isolation should be retained", Connection.TRANSACTION_READ_UNCOMMITTED, connection.getTransactionIsolation()); + } + } +} diff --git a/modules/presto/src/test/resources/initial.sql b/modules/presto/src/test/resources/initial.sql new file mode 100644 index 00000000000..1a3ecdab4f9 --- /dev/null +++ b/modules/presto/src/test/resources/initial.sql @@ -0,0 +1,2 @@ +CREATE TABLE memory.default.test_table(a bigint); +INSERT INTO memory.default.test_table(a) VALUES (12345678909324);