Skip to content

Commit cd987df

Browse files
committed
Add Docker Compose @Serviceconnection support for ClickHouse
1 parent c6619dd commit cd987df

File tree

15 files changed

+467
-3
lines changed

15 files changed

+467
-3
lines changed

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

Lines changed: 2 additions & 0 deletions
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,66 @@
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 javax.sql.DataSource;
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.DataSourceBuilder;
24+
import org.springframework.boot.jdbc.DatabaseDriver;
25+
import org.springframework.boot.testsupport.container.TestImage;
26+
import org.springframework.jdbc.core.JdbcTemplate;
27+
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
31+
/**
32+
* Integration tests for {@link ClickHouseJdbcDockerComposeConnectionDetailsFactory}.
33+
*
34+
* @author Dmytro Nosan
35+
*/
36+
class ClickHouseJdbcDockerComposeConnectionDetailsFactoryIntegrationTests {
37+
38+
@DockerComposeTest(composeFile = "clickhouse-defaults-compose.yaml", image = TestImage.CLICKHOUSE)
39+
void runCreatesJdbcConnectionDetailsWithDefaultValues(JdbcConnectionDetails connectionDetails) {
40+
assertThat(connectionDetails.getUsername()).isEqualTo("default");
41+
assertThat(connectionDetails.getPassword()).isEqualTo("");
42+
assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:clickhouse://").endsWith("/default");
43+
checkDatabaseAccess(connectionDetails);
44+
}
45+
46+
@DockerComposeTest(composeFile = "clickhouse-compose.yaml", image = TestImage.CLICKHOUSE)
47+
void runCreatesJdbcConnectionDetails(JdbcConnectionDetails connectionDetails) {
48+
assertThat(connectionDetails.getUsername()).isEqualTo("myuser");
49+
assertThat(connectionDetails.getPassword()).isEqualTo("secret");
50+
assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:clickhouse://").endsWith("/mydatabase");
51+
checkDatabaseAccess(connectionDetails);
52+
}
53+
54+
private void checkDatabaseAccess(JdbcConnectionDetails connectionDetails) {
55+
DataSource dataSource = DataSourceBuilder.create()
56+
.type(SimpleDriverDataSource.class)
57+
.url(connectionDetails.getJdbcUrl())
58+
.username(connectionDetails.getUsername())
59+
.password(connectionDetails.getPassword())
60+
.build();
61+
JdbcTemplate template = new JdbcTemplate(dataSource);
62+
String sql = DatabaseDriver.CLICKHOUSE.getValidationQuery();
63+
assertThat(template.queryForObject(sql, Integer.class)).isEqualTo(1);
64+
}
65+
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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 Dmytro Nosan
37+
*/
38+
class ClickHouseR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests {
39+
40+
@DockerComposeTest(composeFile = "clickhouse-defaults-compose.yaml", image = TestImage.CLICKHOUSE)
41+
void runCreatesR2dbcConnectionDetailsWithDefaultValues(R2dbcConnectionDetails connectionDetails) {
42+
ConnectionFactoryOptions options = connectionDetails.getConnectionFactoryOptions();
43+
assertCommonOptions(options);
44+
assertThat(options.getRequiredValue(ConnectionFactoryOptions.DATABASE)).isEqualTo("default");
45+
assertThat(options.getRequiredValue(ConnectionFactoryOptions.USER)).isEqualTo("default");
46+
assertThat(options.hasOption(ConnectionFactoryOptions.PASSWORD)).isFalse();
47+
checkDatabaseAccess(connectionDetails);
48+
}
49+
50+
@DockerComposeTest(composeFile = "clickhouse-compose.yaml", image = TestImage.CLICKHOUSE)
51+
void runCreatesR2dbcConnectionDetails(R2dbcConnectionDetails connectionDetails) {
52+
ConnectionFactoryOptions options = connectionDetails.getConnectionFactoryOptions();
53+
assertCommonOptions(options);
54+
assertThat(options.getRequiredValue(ConnectionFactoryOptions.DATABASE)).isEqualTo("mydatabase");
55+
assertThat(options.getRequiredValue(ConnectionFactoryOptions.USER)).isEqualTo("myuser");
56+
assertThat(options.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("secret");
57+
checkDatabaseAccess(connectionDetails);
58+
}
59+
60+
private void assertCommonOptions(ConnectionFactoryOptions options) {
61+
assertThat(options.getRequiredValue(ConnectionFactoryOptions.HOST)).isNotNull();
62+
assertThat(options.getRequiredValue(ConnectionFactoryOptions.PORT)).isNotNull();
63+
assertThat(options.getRequiredValue(ConnectionFactoryOptions.DRIVER)).isEqualTo("clickhouse");
64+
}
65+
66+
private void checkDatabaseAccess(R2dbcConnectionDetails connectionDetails) {
67+
ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions();
68+
ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions);
69+
String sql = DatabaseDriver.CLICKHOUSE.getValidationQuery();
70+
Integer result = Mono.from(connectionFactory.create())
71+
.flatMapMany((connection) -> connection.createStatement(sql).execute())
72+
.flatMap((r) -> r.map((row, rowMetadata) -> row.get(0, Integer.class)))
73+
.blockFirst(Duration.ofSeconds(30));
74+
assertThat(result).isEqualTo(1);
75+
}
76+
77+
}
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,5 @@
1+
services:
2+
database:
3+
image: '{imageName}'
4+
ports:
5+
- '8123'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
/**
22+
* ClickHouse environment details.
23+
*
24+
* @author Dmytro Nosan
25+
*/
26+
class ClickHouseEnvironment {
27+
28+
private final String username;
29+
30+
private final String password;
31+
32+
private final String database;
33+
34+
ClickHouseEnvironment(Map<String, String> env) {
35+
this.username = env.getOrDefault("CLICKHOUSE_USER", "default");
36+
this.password = env.getOrDefault("CLICKHOUSE_PASSWORD", "");
37+
this.database = env.getOrDefault("CLICKHOUSE_DB", "default");
38+
}
39+
40+
String getUsername() {
41+
return this.username;
42+
}
43+
44+
String getPassword() {
45+
return this.password;
46+
}
47+
48+
String getDatabase() {
49+
return this.database;
50+
}
51+
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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 Dmytro Nosan
30+
*/
31+
class ClickHouseJdbcDockerComposeConnectionDetailsFactory
32+
extends DockerComposeConnectionDetailsFactory<JdbcConnectionDetails> {
33+
34+
protected ClickHouseJdbcDockerComposeConnectionDetailsFactory() {
35+
super("clickhouse/clickhouse-server");
36+
}
37+
38+
@Override
39+
protected JdbcConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) {
40+
return new ClickHouseJdbcDockerComposeConnectionDetails(source.getRunningService());
41+
}
42+
43+
/**
44+
* {@link JdbcConnectionDetails} backed by a {@code ClickHouse}
45+
* {@link RunningService}.
46+
*/
47+
static class ClickHouseJdbcDockerComposeConnectionDetails extends DockerComposeConnectionDetails
48+
implements JdbcConnectionDetails {
49+
50+
private static final JdbcUrlBuilder jdbcUrlBuilder = new JdbcUrlBuilder("clickhouse", 8123);
51+
52+
private final ClickHouseEnvironment environment;
53+
54+
private final String jdbcUrl;
55+
56+
ClickHouseJdbcDockerComposeConnectionDetails(RunningService service) {
57+
super(service);
58+
this.environment = new ClickHouseEnvironment(service.env());
59+
this.jdbcUrl = jdbcUrlBuilder.build(service, this.environment.getDatabase());
60+
}
61+
62+
@Override
63+
public String getUsername() {
64+
return this.environment.getUsername();
65+
}
66+
67+
@Override
68+
public String getPassword() {
69+
return this.environment.getPassword();
70+
}
71+
72+
@Override
73+
public String getJdbcUrl() {
74+
return this.jdbcUrl;
75+
}
76+
77+
}
78+
79+
}
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 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 Dmytro Nosan
32+
*/
33+
class ClickHouseR2dbcDockerComposeConnectionDetailsFactory
34+
extends DockerComposeConnectionDetailsFactory<R2dbcConnectionDetails> {
35+
36+
ClickHouseR2dbcDockerComposeConnectionDetailsFactory() {
37+
super("clickhouse/clickhouse-server", "io.r2dbc.spi.ConnectionFactoryOptions");
38+
}
39+
40+
@Override
41+
protected R2dbcConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) {
42+
return new ClickHouseR2dbcDockerComposeConnectionDetails(source.getRunningService());
43+
}
44+
45+
/**
46+
* {@link R2dbcConnectionDetails} backed by a {@code ClickHouse}
47+
* {@link RunningService}.
48+
*/
49+
static class ClickHouseR2dbcDockerComposeConnectionDetails extends DockerComposeConnectionDetails
50+
implements R2dbcConnectionDetails {
51+
52+
private static final ConnectionFactoryOptionsBuilder connectionFactoryOptionsBuilder = new ConnectionFactoryOptionsBuilder(
53+
"clickhouse", 8123);
54+
55+
private final ConnectionFactoryOptions connectionFactoryOptions;
56+
57+
ClickHouseR2dbcDockerComposeConnectionDetails(RunningService service) {
58+
super(service);
59+
ClickHouseEnvironment environment = new ClickHouseEnvironment(service.env());
60+
this.connectionFactoryOptions = connectionFactoryOptionsBuilder.build(service, environment.getDatabase(),
61+
environment.getUsername(), environment.getPassword());
62+
}
63+
64+
@Override
65+
public ConnectionFactoryOptions getConnectionFactoryOptions() {
66+
return this.connectionFactoryOptions;
67+
}
68+
69+
}
70+
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
/**
18+
* Auto-configuration for Docker Compose ClickHouse service connections.
19+
*/
20+
package org.springframework.boot.docker.compose.service.connection.clickhouse;

0 commit comments

Comments
 (0)