Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protected ResultSet performQuery(JdbcDatabaseContainer<?> container, String sql)
return resultSet;
}

protected DataSource getDataSource(JdbcDatabaseContainer<?> container) {
protected final HikariDataSource getDataSource(JdbcDatabaseContainer<?> container) {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(container.getJdbcUrl());
hikariConfig.setUsername(container.getUsername());
Expand Down
13 changes: 13 additions & 0 deletions modules/questdb/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
description = "Testcontainers :: QuestDB"

dependencies {
api project(':testcontainers')
api project(':jdbc')

testImplementation project(':jdbc-test')
testImplementation project(':test-support')
testImplementation 'org.postgresql:postgresql:42.4.0'
testImplementation group: 'org.questdb', name: 'questdb', version: '6.4.3-jdk8'
testImplementation 'org.awaitility:awaitility:4.2.0'
testImplementation 'org.apache.httpcomponents:httpclient:4.5.13'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.testcontainers.containers;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
package org.testcontainers.containers;
package org.testcontainers.questdb;

Going ahead, new modules should use a package name following org.testcontainers.{module-name}, to avoid Split Package issues with JMS.


import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
import org.testcontainers.utility.DockerImageName;

import java.time.Duration;

public final class QuestDBContainer extends JdbcDatabaseContainer<QuestDBContainer> {

private static final int PGWIRE_PORT = 8812;

private static final int ILP_PORT = 9009;

private static final int REST_PORT = 9000;

// commit lag 1s is more suitable for testing
private static final int DEFAULT_COMMIT_LAG_MS = 1000;

private static final String DEFAULT_USERNAME = "admin";

private static final String DEFAULT_PASSWORD = "quest";

private static final String SQL_TEST_STRING = "select * from long_sequence(1)";

private static final String DRIVER_FQCN = "org.postgresql.Driver";

public QuestDBContainer(DockerImageName dockerImageName) {
super(dockerImageName);
addExposedPort(PGWIRE_PORT);
addExposedPort(ILP_PORT);
addExposedPort(REST_PORT);
withCommitLag(DEFAULT_COMMIT_LAG_MS);
this.waitStrategy =
new HttpWaitStrategy()
.forStatusCode(200)
.forResponsePredicate("Ok."::equals)
.withStartupTimeout(Duration.ofMinutes(1));
}

public QuestDBContainer withCommitLag(int commitLagMillis) {
addEnv("QDB_CAIRO_COMMIT_LAG", String.valueOf(commitLagMillis));
return this;
}

@Override
public String getDriverClassName() {
return DRIVER_FQCN;
}

@Override
public String getJdbcUrl() {
return "jdbc:postgresql://" + getHost() + ":" + getMappedPort(PGWIRE_PORT) + "/";
}

@Override
public String getUsername() {
return DEFAULT_USERNAME;
}

@Override
public String getPassword() {
return DEFAULT_PASSWORD;
}

@Override
protected String getTestQueryString() {
return SQL_TEST_STRING;
}

public String getIlpUrl() {
return getHost() + ":" + getMappedPort(ILP_PORT);
}

public String getHttpUrl() {
return "http://" + getHost() + ":" + getMappedPort(REST_PORT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.testcontainers.junit.questdb;

import org.testcontainers.utility.DockerImageName;

public interface QuestDBTestImages {
DockerImageName QUESTDB_TEST_IMAGE = DockerImageName.parse("questdb/questdb:6.4.3");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.testcontainers.junit.questdb;

import com.zaxxer.hikari.HikariDataSource;
import io.questdb.client.Sender;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.junit.Test;
import org.testcontainers.containers.QuestDBContainer;
import org.testcontainers.db.AbstractContainerDatabaseTest;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public final class SimpleQuestDBTest extends AbstractContainerDatabaseTest {

private static final String TABLE_NAME = "mytable";

@Test
public void testConnectivity() throws SQLException {
try (QuestDBContainer questdb = new QuestDBContainer(QuestDBTestImages.QUESTDB_TEST_IMAGE)) {
questdb.start();

ResultSet resultSet = performQuery(questdb, "SELECT 1");
int resultSetInt = resultSet.getInt(1);
assertEquals("A basic SELECT query succeeds", 1, resultSetInt);
}
}

@Test
public void testPgWire() throws SQLException {
try (QuestDBContainer questdb = new QuestDBContainer(QuestDBTestImages.QUESTDB_TEST_IMAGE)) {
questdb.start();
populateByInfluxLineProtocol(questdb, 1_000);
try (
HikariDataSource ds = getDataSource(questdb);
Connection conn = ds.getConnection();
PreparedStatement stmt = conn.prepareStatement("select count(*) from " + TABLE_NAME)
) {
await()
.untilAsserted(() -> {
try (ResultSet rs = stmt.executeQuery()) {
assertTrue(rs.next());
assertEquals(1_000, rs.getInt(1));
}
});
}
}
}

@Test
public void testRest() throws IOException {
try (QuestDBContainer questdb = new QuestDBContainer(QuestDBTestImages.QUESTDB_TEST_IMAGE)) {
questdb.start();
populateByInfluxLineProtocol(questdb, 1_000);
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
String encodedSql = URLEncoder.encode("select * from " + TABLE_NAME, "UTF-8");
HttpGet httpGet = new HttpGet(questdb.getHttpUrl() + "/exec?query=" + encodedSql);
await()
.untilAsserted(() -> {
try (CloseableHttpResponse response = client.execute(httpGet)) {
assertEquals(200, response.getStatusLine().getStatusCode());
String json = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
assertTrue(
"questdb response '" + json + "' does not contain expected count 1000",
json.contains("\"count\":1000")
);
}
});
}
}
}

private static void populateByInfluxLineProtocol(QuestDBContainer questdb, int rowCount) {
try (Sender sender = Sender.builder().address(questdb.getIlpUrl()).build()) {
for (int i = 0; i < rowCount; i++) {
sender
.table(TABLE_NAME)
.symbol("sym", "sym1" + i)
.stringColumn("str", "str1" + i)
.longColumn("long", i)
.atNow();
}
}
}
}