Skip to content

Conversation

@codepitbull
Copy link
Contributor

Why ?

The current implementation of the Clickhouse-Container only supports the Yandex-JDBC-driver and enforces it to be on the classpath.
It also doesn't export trhe MYSQL-port available in Clickhouse.
The base image includedd is also relying on an old version of Clickhouse that was missing some compatibility fixes for the MySQL-JDBC-driver.

Since I also wanted to test the Vert.x MySQL-driver against Clickhouse I needed to fix these things.

What ?

This PR provides the following things:

  • ClickHouseContainerJdbcMysql for using the MySQL-JDBC-driver (relys on the MySQL-port being exposed and the driver being on the classpath)
  • ClickHouseContainerJdbcYandex for using hte Yandeex-JDBC-driver (relys on the HTTP-port being exposed and the driver being on the classpath)
  • ClickHouseContainer for accessing the HTTP and MYSQL-ports without having to rely on the presence of one of the JDBC-drivers


public static final Integer HTTP_PORT = 8123;
public static final Integer NATIVE_PORT = 9000;
public class ClickHouseContainer<SELF extends ClickHouseContainer<SELF>> extends GenericContainer<SELF> {
Copy link
Member

Choose a reason for hiding this comment

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

It's okay and more consistent with other modern modules to leave out the generic type here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do :)

Copy link
Member

Choose a reason for hiding this comment

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

Also, removing extends JdbcDatabaseContainer is a breaking change - please revert

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is probably a naming messup on my side.
There are now 3 containers:
ClickHouseContainerJdbcMysql (extends JdbcDatabaseContainer) => Requires the mysql-driver to work
ClickHouseContainerJdbcYandex (extends JdbcDatabaseContainer) => Requires the yandex-driver to work
ClickHouseContainer => Not a JDBC container since it is used to test via the raw wire protocl (mysql) and the http api, this shouldn't rely on having a JDBC driver on the path.

What would you suggest naming wise?

Copy link
Member

Choose a reason for hiding this comment

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

The naming is okay, just we can't drop the parent class like that - not everyone starts containers with the JDBC URL integration, some may have used it as var ch = new ClickHouseContainer(); ch.start(); ch.getJdbcUrl()

Copy link
Member

@kiview kiview left a comment

Choose a reason for hiding this comment

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

Hey @codepitbull, sorry for taking our time with this PR.

PR looks pretty good, just having some general remarks.
I was wondering if the 3 different classes like this are really necessary, or if we couldn't just use a single class and its type configurable (with the type just resulting in a different value for driverClassName after all). But maybe I am missing something, I have no experience with Clickhouse and look at it purely from the Testcontainers perspective.

@Override
public Set<Integer> getLivenessCheckPortNumbers() {
return Sets.newHashSet(HTTP_PORT);
return Sets.newHashSet(ClickHouseInit.HTTP_PORT);
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
return Sets.newHashSet(ClickHouseInit.HTTP_PORT);
return Collections.singleton(ClickHouseInit.HTTP_PORT);

Comment on lines 41 to 43
public String getTestQueryString() {
return TEST_QUERY;
return ClickHouseInit.TEST_QUERY;
}
Copy link
Member

Choose a reason for hiding this comment

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

This method is not necessary anymore if the class does not extend JdbcContainer anymore. Maybe we deprecate it in this PR or remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed it

Comment on lines 21 to 23
public static final String databaseName = "default";
public static final String username = "default";
public static final String password = "";
Copy link
Member

Choose a reason for hiding this comment

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

Why aren't those uppercase?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because I have been doing Go for too long ... geez, how stuff like that seeps into daily coding.
Will change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.


import java.time.Duration;

public class ClickHouseInit {
Copy link
Member

Choose a reason for hiding this comment

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

In this class, we should only make the constants public that need to be public and keep the rest package-protected. The class itself can also be package-protected, or?

Copy link
Contributor Author

@codepitbull codepitbull Sep 29, 2021

Choose a reason for hiding this comment

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

Main reason this class isn't package-protected is that I was trying to follow the package layout you picked which moves the junit-tests into a dedicated folder.
From there I can't access the class to get the constants for testing.
If you are ok with merging the packages I can make it protected.

Copy link
Member

Choose a reason for hiding this comment

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

I think #4038 (comment) will make it obsolete

new HttpWaitStrategy()
.forStatusCode(200)
.forPort(HTTP_PORT)
.forResponsePredicate(responseBody -> "Ok.".equals(responseBody))
Copy link
Member

Choose a reason for hiding this comment

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

Is this response important if we already get 200 status code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I prefer to be strict with these (bad experiences with APIs returning 200 but with a failure message).
In our own driver we check for the "Ok." reply since it is explicitly mentioned in the clickhouse docs as the return value that indicates success.

Copy link
Member

Choose a reason for hiding this comment

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

I am not sure I share the preference :) It seems to be a free text value, not even some structured response (e.g. JSON).

I would rely on ClickHouse devs being the good folks who understand the meaning of 200 and would never return 200 in case of an error :)

@Override
public boolean supports(String databaseType) {
return databaseType.equals(ClickHouseContainer.NAME);
return databaseType.equals(ClickHouseInit.JDBC_NAME_YANDEX) || databaseType.equals(ClickHouseInit.JDBC_NAME_MYSQL);
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't this collide with the normal mysql JDBC URL if someone has both modules on the classpath? In this case, they would get either container.

Copy link
Member

Choose a reason for hiding this comment

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

It would be interesting to see what happens in this case, but I think it's a good point. I wonder if we should:
(a) make the ContainerDatabaseDriver fail fast in this scenario, or
(b) require a different string to be used, e.g. tc:clickhousemysql. This seems less nice though, as it breaks an implicit contract about use of tc: in URLs.

Copy link
Member

Choose a reason for hiding this comment

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

While (a) sounds reasonable (and I wonder why we decided to return any matching container in case of collision) how would a user that has both dependencies on their classpath (for a valid reason) deal with this? Our tests would also fail, or?

clickhouse.start();
public void testRawMysql() throws Throwable {
ClickHouseContainer clickHouseContainer = new ClickHouseContainer();
clickHouseContainer.start();
Copy link
Member

Choose a reason for hiding this comment

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

Why did you remove the try-with-resource? We use this to close the container after the test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed


ResultSet resultSet = performQuery(clickhouse, "SELECT 1");
MySQLConnectOptions connectOptions = new MySQLConnectOptions()
.setPort(clickHouseContainer.getMappedPort(ClickHouseInit.MYSQL_PORT))
Copy link
Member

Choose a reason for hiding this comment

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

I feel like having the constant in ClickHousInit makes it a bit harder to discover for users.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The constants are shared between all three.

What do you think about adding this to ClickHouseContainer?

    public Integer getHttpPort() {
        return getMappedPort(ClickHouseInit.HTTP_PORT);
    }

    public Integer getNativePort() {
        return getMappedPort(ClickHouseInit.NATIVE_PORT);
    }

    public Integer getMysqlPort() {
        return getMappedPort(ClickHouseInit.MYSQL_PORT);
    } 


public static final String DEFAULT_TAG = "21.3.8.76";

public static void Init(GenericContainer<?> container) {
Copy link
Member

Choose a reason for hiding this comment

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

we don't do this in Java =P

Suggested change
public static void Init(GenericContainer<?> container) {
public static void init(GenericContainer<?> container) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.


import org.testcontainers.utility.DockerImageName;

public abstract class AbstractClickHouseContainerJdbc extends JdbcDatabaseContainer {
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
public abstract class AbstractClickHouseContainerJdbc extends JdbcDatabaseContainer {
abstract class AbstractClickHouseContainerJdbc extends JdbcDatabaseContainer {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.


@Override
public AbstractClickHouseContainerJdbc withUrlParam(String paramName, String paramValue) {
throw new UnsupportedOperationException("The ClickHouse does not support this");
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh, yes, good point.

@bsideup
Copy link
Member

bsideup commented Sep 27, 2021

@codepitbull after reviewing the PR, I got a feeling that it can be done in a much easier way. We could keep the original ClickHouseContainer and have a couple of "proxy" classes that will take ClickHouseContainer as an argument and implement the required methods.

The getJdbcUrl() can either be overloaded with enum Flavor {MYSQL,CLICKHOUSE} or we could add getMySQLJdbcUrl() or similar method.

This way we can dramatically reduce the size of this PR, avoid breaking changes and keep it simple (eg no need for ClickHouseInit at all). WDYT?

@codepitbull
Copy link
Contributor Author

Hey @codepitbull, sorry for taking our time with this PR.

PR looks pretty good, just having some general remarks. I was wondering if the 3 different classes like this are really necessary, or if we couldn't just use a single class and its type configurable (with the type just resulting in a different value for driverClassName after all). But maybe I am missing something, I have no experience with Clickhouse and look at it purely from the Testcontainers perspective.

@kiview see my answer in the other thread:
Short version:
ClickHouseContainer is using the raw protocols and should not rely on having a JDBC-driver on the classpath.
For the other two it's a question of style and I will rework it as you need it. My personal opinion on this is that I prefer separate classes because they rely on different JDBC-drivers. I am not a huge fan to have this configurable, that's why I moved all shared code into AbstractClickHouseContainerJdbc.

@codepitbull
Copy link
Contributor Author

@codepitbull after reviewing the PR, I got a feeling that it can be done in a much easier way. We could keep the original ClickHouseContainer and have a couple of "proxy" classes that will take ClickHouseContainer as an argument and implement the required methods.

The getJdbcUrl() can either be overloaded with enum Flavor {MYSQL,CLICKHOUSE} or we could add getMySQLJdbcUrl() or similar method.

This way we can dramatically reduce the size of this PR, avoid breaking changes and keep it simple (eg no need for ClickHouseInit at all). WDYT?

Should be possible, but it will take a while until I get to this.

@eddumelendez
Copy link
Member

Closing due to there is no progress on this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants