Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QuestDB Container module #5606

Closed
wants to merge 5 commits into from
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.
Jump to
Jump to file
Failed to load files.
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
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();
}
}
}
}