diff --git a/docs/modules/databases/index.md b/docs/modules/databases/index.md index 2511502f650..995757e6a64 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:327://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..3f52c02a922 --- /dev/null +++ b/docs/modules/databases/presto.md @@ -0,0 +1,85 @@ +# Presto Module + +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 presto = new PrestoContainer(); + + @Test + public void queryMemoryAndTpchConnectors() throws SQLException { + try (Connection connection = presto.createConnection(); + Statement statement = connection.createStatement()) { + // Prepare data + statement.execute("CREATE TABLE memory.default.table_with_array AS SELECT 1 id, ARRAY[4, 2, 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 ")) { + Set actualElements = new HashSet<>(); + while (resultSet.next()) { + actualElements.add(resultSet.getInt("element")); + } + Assert.assertEquals(ImmutableSet.of(4, 2, 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/modules/jdbc-test/build.gradle b/modules/jdbc-test/build.gradle index 842f2a761eb..8cdeeb7e487 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:327' 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..6a16dd5f0c0 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:327://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..720c77c0b17 --- /dev/null +++ b/modules/presto/build.gradle @@ -0,0 +1,8 @@ +description = "Testcontainers :: JDBC :: Presto" + +dependencies { + compile project(':jdbc') + + testCompile 'io.prestosql:presto-jdbc:327' + 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..d237a7d30a1 --- /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.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 = "327"; + + 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..f318331e3c0 --- /dev/null +++ b/modules/presto/src/test/java/org/testcontainers/containers/PrestoContainerTest.java @@ -0,0 +1,103 @@ +/* + * 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 org.testcontainers.containers; + +import com.google.common.collect.ImmutableSet; +import org.junit.Assert; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author findepi + */ +public class PrestoContainerTest { + @Test + public void testSimple() throws Exception { + try (PrestoContainer container = new PrestoContainer<>()) { + container.start(); + try (Connection connection = container.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 = "325"; + assertNotEquals(prestoVersion, PrestoContainer.DEFAULT_TAG); + try (PrestoContainer container = new PrestoContainer<>("prestosql/presto:" + prestoVersion)) { + container.start(); + try (Connection connection = container.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", "325", resultSet.getString("node_version")); + } + } + } + + @Test + public void testQueryMemoryAndTpch() throws SQLException { + try (PrestoContainer container = new PrestoContainer<>()) { + container.start(); + try (Connection connection = container.createConnection(); + Statement statement = connection.createStatement()) { + // Prepare data + statement.execute("CREATE TABLE memory.default.table_with_array AS SELECT 1 id, ARRAY[4, 2, 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 ")) { + Set actualElements = new HashSet<>(); + while (resultSet.next()) { + actualElements.add(resultSet.getInt("element")); + } + Assert.assertEquals(ImmutableSet.of(4, 2, 42), actualElements); + } + } + } + } + + @Test + public void testInitScript() throws Exception { + try (PrestoContainer container = new PrestoContainer<>()) { + container.withInitScript("initial.sql"); + container.start(); + try (Connection connection = container.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()); + } + } + } +} 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);