Skip to content

Commit 27e8f14

Browse files
committed
Polish "Add container support for ClickHouse"
See spring-projectsgh-42837
1 parent d9dfb03 commit 27e8f14

File tree

14 files changed

+497
-3
lines changed

14 files changed

+497
-3
lines changed

spring-boot-project/spring-boot-docker-compose/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ dependencies {
1919
dockerTestImplementation("org.junit.jupiter:junit-jupiter")
2020
dockerTestImplementation("org.testcontainers:testcontainers")
2121

22+
dockerTestRuntimeOnly("com.clickhouse:clickhouse-jdbc")
23+
dockerTestRuntimeOnly("com.clickhouse:clickhouse-r2dbc")
2224
dockerTestRuntimeOnly("com.microsoft.sqlserver:mssql-jdbc")
2325
dockerTestRuntimeOnly("com.oracle.database.r2dbc:oracle-r2dbc")
2426
dockerTestRuntimeOnly("io.r2dbc:r2dbc-mssql")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docker.compose.service.connection.clickhouse;
18+
19+
import java.sql.Driver;
20+
21+
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
22+
import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest;
23+
import org.springframework.boot.jdbc.DatabaseDriver;
24+
import org.springframework.boot.testsupport.container.TestImage;
25+
import org.springframework.jdbc.core.JdbcTemplate;
26+
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
27+
import org.springframework.util.ClassUtils;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
31+
/**
32+
* Integration tests for {@link ClickHouseJdbcDockerComposeConnectionDetailsFactory}.
33+
*
34+
* @author Stephane Nicoll
35+
*/
36+
class ClickHouseJdbcDockerComposeConnectionDetailsFactoryIntegrationTests {
37+
38+
@DockerComposeTest(composeFile = "clickhouse-compose.yaml", image = TestImage.CLICKHOUSE)
39+
void runCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) throws ClassNotFoundException {
40+
assertConnectionDetails(connectionDetails);
41+
checkDatabaseAccess(connectionDetails);
42+
}
43+
44+
@DockerComposeTest(composeFile = "clickhouse-bitnami-compose.yaml", image = TestImage.BITNAMI_CLICKHOUSE)
45+
void runWithBitnamiImageCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) {
46+
assertConnectionDetails(connectionDetails);
47+
// See https://github.com/bitnami/containers/issues/73550
48+
// checkDatabaseAccess(connectionDetails);
49+
}
50+
51+
private void assertConnectionDetails(JdbcConnectionDetails connectionDetails) {
52+
assertThat(connectionDetails.getUsername()).isEqualTo("myuser");
53+
assertThat(connectionDetails.getPassword()).isEqualTo("secret");
54+
assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:clickhouse://").endsWith("/mydatabase");
55+
}
56+
57+
@SuppressWarnings("unchecked")
58+
private void checkDatabaseAccess(JdbcConnectionDetails connectionDetails) throws ClassNotFoundException {
59+
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
60+
dataSource.setUrl(connectionDetails.getJdbcUrl());
61+
dataSource.setUsername(connectionDetails.getUsername());
62+
dataSource.setPassword(connectionDetails.getPassword());
63+
dataSource.setDriverClass((Class<? extends Driver>) ClassUtils.forName(connectionDetails.getDriverClassName(),
64+
getClass().getClassLoader()));
65+
JdbcTemplate template = new JdbcTemplate(dataSource);
66+
assertThat(template.queryForObject(DatabaseDriver.CLICKHOUSE.getValidationQuery(), Integer.class)).isEqualTo(1);
67+
}
68+
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docker.compose.service.connection.clickhouse;
18+
19+
import java.time.Duration;
20+
21+
import io.r2dbc.spi.ConnectionFactories;
22+
import io.r2dbc.spi.ConnectionFactory;
23+
import io.r2dbc.spi.ConnectionFactoryOptions;
24+
import reactor.core.publisher.Mono;
25+
26+
import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails;
27+
import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest;
28+
import org.springframework.boot.jdbc.DatabaseDriver;
29+
import org.springframework.boot.testsupport.container.TestImage;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
33+
/**
34+
* Integration tests for {@link ClickHouseR2dbcDockerComposeConnectionDetailsFactory}.
35+
*
36+
* @author Stephane Nicoll
37+
*/
38+
class ClickHouseR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests {
39+
40+
@DockerComposeTest(composeFile = "clickhouse-compose.yaml", image = TestImage.CLICKHOUSE)
41+
void runCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) {
42+
assertConnectionDetails(connectionDetails);
43+
checkDatabaseAccess(connectionDetails);
44+
}
45+
46+
@DockerComposeTest(composeFile = "clickhouse-bitnami-compose.yaml", image = TestImage.BITNAMI_CLICKHOUSE)
47+
void runWithBitnamiImageCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) {
48+
assertConnectionDetails(connectionDetails);
49+
// See https://github.com/bitnami/containers/issues/73550
50+
// checkDatabaseAccess(connectionDetails);
51+
}
52+
53+
private void assertConnectionDetails(R2dbcConnectionDetails connectionDetails) {
54+
ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions();
55+
assertThat(connectionFactoryOptions.toString()).contains("database=mydatabase", "driver=clickhouse",
56+
"password=REDACTED", "user=myuser");
57+
assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("secret");
58+
}
59+
60+
private void checkDatabaseAccess(R2dbcConnectionDetails connectionDetails) {
61+
ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions();
62+
ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions);
63+
String sql = DatabaseDriver.CLICKHOUSE.getValidationQuery();
64+
Integer result = Mono.from(connectionFactory.create())
65+
.flatMapMany((connection) -> connection.createStatement(sql).execute())
66+
.flatMap((r) -> r.map((row, rowMetadata) -> row.get(0, Integer.class)))
67+
.blockFirst(Duration.ofSeconds(30));
68+
assertThat(result).isEqualTo(1);
69+
}
70+
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
services:
2+
database:
3+
image: '{imageName}'
4+
ports:
5+
- '8123'
6+
environment:
7+
- 'CLICKHOUSE_USER=myuser'
8+
- 'CLICKHOUSE_PASSWORD=secret'
9+
- 'CLICKHOUSE_DB=mydatabase'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
services:
2+
database:
3+
image: '{imageName}'
4+
ports:
5+
- '8123'
6+
environment:
7+
- 'CLICKHOUSE_USER=myuser'
8+
- 'CLICKHOUSE_PASSWORD=secret'
9+
- 'CLICKHOUSE_DB=mydatabase'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docker.compose.service.connection.clickhouse;
18+
19+
import java.util.Map;
20+
21+
import org.springframework.util.Assert;
22+
import org.springframework.util.StringUtils;
23+
24+
/**
25+
* ClickHouse environment details.
26+
*
27+
* @author Stephane Nicoll
28+
*/
29+
class ClickHouseEnvironment {
30+
31+
private final String username;
32+
33+
private final String password;
34+
35+
private final String database;
36+
37+
ClickHouseEnvironment(Map<String, String> env) {
38+
this.username = env.getOrDefault("CLICKHOUSE_USER", "default");
39+
this.password = extractPassword(env);
40+
this.database = env.getOrDefault("CLICKHOUSE_DB", "default");
41+
}
42+
43+
private String extractPassword(Map<String, String> env) {
44+
boolean allowEmpty = Boolean.parseBoolean(env.getOrDefault("ALLOW_EMPTY_PASSWORD", Boolean.FALSE.toString()));
45+
String password = env.get("CLICKHOUSE_PASSWORD");
46+
Assert.state(StringUtils.hasLength(password) || allowEmpty, "No ClickHouse password found");
47+
return (password != null) ? password : "";
48+
}
49+
50+
String getUsername() {
51+
return this.username;
52+
}
53+
54+
String getPassword() {
55+
return this.password;
56+
}
57+
58+
String getDatabase() {
59+
return this.database;
60+
}
61+
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docker.compose.service.connection.clickhouse;
18+
19+
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
20+
import org.springframework.boot.docker.compose.core.RunningService;
21+
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory;
22+
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource;
23+
import org.springframework.boot.docker.compose.service.connection.jdbc.JdbcUrlBuilder;
24+
25+
/**
26+
* {@link DockerComposeConnectionDetailsFactory} to create {@link JdbcConnectionDetails}
27+
* for a {@code clickhouse} service.
28+
*
29+
* @author Stephane Nicoll
30+
*/
31+
class ClickHouseJdbcDockerComposeConnectionDetailsFactory
32+
extends DockerComposeConnectionDetailsFactory<JdbcConnectionDetails> {
33+
34+
private static final String[] CLICKHOUSE_CONTAINER_NAMES = { "clickhouse/clickhouse-server", "bitnami/clickhouse" };
35+
36+
protected ClickHouseJdbcDockerComposeConnectionDetailsFactory() {
37+
super(CLICKHOUSE_CONTAINER_NAMES);
38+
}
39+
40+
@Override
41+
protected JdbcConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) {
42+
return new ClickhouseJdbcDockerComposeConnectionDetails(source.getRunningService());
43+
}
44+
45+
/**
46+
* {@link JdbcConnectionDetails} backed by a {@code clickhouse}
47+
* {@link RunningService}.
48+
*/
49+
static class ClickhouseJdbcDockerComposeConnectionDetails extends DockerComposeConnectionDetails
50+
implements JdbcConnectionDetails {
51+
52+
private static final JdbcUrlBuilder jdbcUrlBuilder = new JdbcUrlBuilder("clickhouse", 8123);
53+
54+
private final ClickHouseEnvironment environment;
55+
56+
private final String jdbcUrl;
57+
58+
ClickhouseJdbcDockerComposeConnectionDetails(RunningService service) {
59+
super(service);
60+
this.environment = new ClickHouseEnvironment(service.env());
61+
this.jdbcUrl = jdbcUrlBuilder.build(service, this.environment.getDatabase());
62+
}
63+
64+
@Override
65+
public String getUsername() {
66+
return this.environment.getUsername();
67+
}
68+
69+
@Override
70+
public String getPassword() {
71+
return this.environment.getPassword();
72+
}
73+
74+
@Override
75+
public String getJdbcUrl() {
76+
return this.jdbcUrl;
77+
}
78+
79+
}
80+
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docker.compose.service.connection.clickhouse;
18+
19+
import io.r2dbc.spi.ConnectionFactoryOptions;
20+
21+
import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails;
22+
import org.springframework.boot.docker.compose.core.RunningService;
23+
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory;
24+
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource;
25+
import org.springframework.boot.docker.compose.service.connection.r2dbc.ConnectionFactoryOptionsBuilder;
26+
27+
/**
28+
* {@link DockerComposeConnectionDetailsFactory} to create {@link R2dbcConnectionDetails}
29+
* for a {@code clickhouse} service.
30+
*
31+
* @author Stephane Nicoll
32+
*/
33+
class ClickHouseR2dbcDockerComposeConnectionDetailsFactory
34+
extends DockerComposeConnectionDetailsFactory<R2dbcConnectionDetails> {
35+
36+
private static final String[] CLICKHOUSE_CONTAINER_NAMES = { "clickhouse/clickhouse-server", "bitnami/clickhouse" };
37+
38+
ClickHouseR2dbcDockerComposeConnectionDetailsFactory() {
39+
super(CLICKHOUSE_CONTAINER_NAMES, "io.r2dbc.spi.ConnectionFactoryOptions");
40+
}
41+
42+
@Override
43+
protected R2dbcConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) {
44+
return new ClickhouseDbR2dbcDockerComposeConnectionDetails(source.getRunningService());
45+
}
46+
47+
/**
48+
* {@link R2dbcConnectionDetails} backed by a {@code clickhouse}
49+
* {@link RunningService}.
50+
*/
51+
static class ClickhouseDbR2dbcDockerComposeConnectionDetails extends DockerComposeConnectionDetails
52+
implements R2dbcConnectionDetails {
53+
54+
private static final ConnectionFactoryOptionsBuilder connectionFactoryOptionsBuilder = new ConnectionFactoryOptionsBuilder(
55+
"clickhouse", 8123);
56+
57+
private final ConnectionFactoryOptions connectionFactoryOptions;
58+
59+
ClickhouseDbR2dbcDockerComposeConnectionDetails(RunningService service) {
60+
super(service);
61+
ClickHouseEnvironment environment = new ClickHouseEnvironment(service.env());
62+
this.connectionFactoryOptions = connectionFactoryOptionsBuilder.build(service, environment.getDatabase(),
63+
environment.getUsername(), environment.getPassword());
64+
}
65+
66+
@Override
67+
public ConnectionFactoryOptions getConnectionFactoryOptions() {
68+
return this.connectionFactoryOptions;
69+
}
70+
71+
}
72+
73+
}

0 commit comments

Comments
 (0)