14 changed files with 497 additions and 3 deletions
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
/* |
||||
* Copyright 2012-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.docker.compose.service.connection.clickhouse; |
||||
|
||||
import java.sql.Driver; |
||||
|
||||
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; |
||||
import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; |
||||
import org.springframework.boot.jdbc.DatabaseDriver; |
||||
import org.springframework.boot.testsupport.container.TestImage; |
||||
import org.springframework.jdbc.core.JdbcTemplate; |
||||
import org.springframework.jdbc.datasource.SimpleDriverDataSource; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Integration tests for {@link ClickHouseJdbcDockerComposeConnectionDetailsFactory}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class ClickHouseJdbcDockerComposeConnectionDetailsFactoryIntegrationTests { |
||||
|
||||
@DockerComposeTest(composeFile = "clickhouse-compose.yaml", image = TestImage.CLICKHOUSE) |
||||
void runCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) throws ClassNotFoundException { |
||||
assertConnectionDetails(connectionDetails); |
||||
checkDatabaseAccess(connectionDetails); |
||||
} |
||||
|
||||
@DockerComposeTest(composeFile = "clickhouse-bitnami-compose.yaml", image = TestImage.BITNAMI_CLICKHOUSE) |
||||
void runWithBitnamiImageCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) { |
||||
assertConnectionDetails(connectionDetails); |
||||
// See https://github.com/bitnami/containers/issues/73550
|
||||
// checkDatabaseAccess(connectionDetails);
|
||||
} |
||||
|
||||
private void assertConnectionDetails(JdbcConnectionDetails connectionDetails) { |
||||
assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); |
||||
assertThat(connectionDetails.getPassword()).isEqualTo("secret"); |
||||
assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:clickhouse://").endsWith("/mydatabase"); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private void checkDatabaseAccess(JdbcConnectionDetails connectionDetails) throws ClassNotFoundException { |
||||
SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); |
||||
dataSource.setUrl(connectionDetails.getJdbcUrl()); |
||||
dataSource.setUsername(connectionDetails.getUsername()); |
||||
dataSource.setPassword(connectionDetails.getPassword()); |
||||
dataSource.setDriverClass((Class<? extends Driver>) ClassUtils.forName(connectionDetails.getDriverClassName(), |
||||
getClass().getClassLoader())); |
||||
JdbcTemplate template = new JdbcTemplate(dataSource); |
||||
assertThat(template.queryForObject(DatabaseDriver.CLICKHOUSE.getValidationQuery(), Integer.class)).isEqualTo(1); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
/* |
||||
* Copyright 2012-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.docker.compose.service.connection.clickhouse; |
||||
|
||||
import java.time.Duration; |
||||
|
||||
import io.r2dbc.spi.ConnectionFactories; |
||||
import io.r2dbc.spi.ConnectionFactory; |
||||
import io.r2dbc.spi.ConnectionFactoryOptions; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; |
||||
import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; |
||||
import org.springframework.boot.jdbc.DatabaseDriver; |
||||
import org.springframework.boot.testsupport.container.TestImage; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Integration tests for {@link ClickHouseR2dbcDockerComposeConnectionDetailsFactory}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class ClickHouseR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests { |
||||
|
||||
@DockerComposeTest(composeFile = "clickhouse-compose.yaml", image = TestImage.CLICKHOUSE) |
||||
void runCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) { |
||||
assertConnectionDetails(connectionDetails); |
||||
checkDatabaseAccess(connectionDetails); |
||||
} |
||||
|
||||
@DockerComposeTest(composeFile = "clickhouse-bitnami-compose.yaml", image = TestImage.BITNAMI_CLICKHOUSE) |
||||
void runWithBitnamiImageCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) { |
||||
assertConnectionDetails(connectionDetails); |
||||
// See https://github.com/bitnami/containers/issues/73550
|
||||
// checkDatabaseAccess(connectionDetails);
|
||||
} |
||||
|
||||
private void assertConnectionDetails(R2dbcConnectionDetails connectionDetails) { |
||||
ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); |
||||
assertThat(connectionFactoryOptions.toString()).contains("database=mydatabase", "driver=clickhouse", |
||||
"password=REDACTED", "user=myuser"); |
||||
assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("secret"); |
||||
} |
||||
|
||||
private void checkDatabaseAccess(R2dbcConnectionDetails connectionDetails) { |
||||
ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); |
||||
ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); |
||||
String sql = DatabaseDriver.CLICKHOUSE.getValidationQuery(); |
||||
Integer result = Mono.from(connectionFactory.create()) |
||||
.flatMapMany((connection) -> connection.createStatement(sql).execute()) |
||||
.flatMap((r) -> r.map((row, rowMetadata) -> row.get(0, Integer.class))) |
||||
.blockFirst(Duration.ofSeconds(30)); |
||||
assertThat(result).isEqualTo(1); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
services: |
||||
database: |
||||
image: '{imageName}' |
||||
ports: |
||||
- '8123' |
||||
environment: |
||||
- 'CLICKHOUSE_USER=myuser' |
||||
- 'CLICKHOUSE_PASSWORD=secret' |
||||
- 'CLICKHOUSE_DB=mydatabase' |
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
services: |
||||
database: |
||||
image: '{imageName}' |
||||
ports: |
||||
- '8123' |
||||
environment: |
||||
- 'CLICKHOUSE_USER=myuser' |
||||
- 'CLICKHOUSE_PASSWORD=secret' |
||||
- 'CLICKHOUSE_DB=mydatabase' |
||||
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
/* |
||||
* Copyright 2012-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.docker.compose.service.connection.clickhouse; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* ClickHouse environment details. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class ClickHouseEnvironment { |
||||
|
||||
private final String username; |
||||
|
||||
private final String password; |
||||
|
||||
private final String database; |
||||
|
||||
ClickHouseEnvironment(Map<String, String> env) { |
||||
this.username = env.getOrDefault("CLICKHOUSE_USER", "default"); |
||||
this.password = extractPassword(env); |
||||
this.database = env.getOrDefault("CLICKHOUSE_DB", "default"); |
||||
} |
||||
|
||||
private String extractPassword(Map<String, String> env) { |
||||
boolean allowEmpty = Boolean.parseBoolean(env.getOrDefault("ALLOW_EMPTY_PASSWORD", Boolean.FALSE.toString())); |
||||
String password = env.get("CLICKHOUSE_PASSWORD"); |
||||
Assert.state(StringUtils.hasLength(password) || allowEmpty, "No ClickHouse password found"); |
||||
return (password != null) ? password : ""; |
||||
} |
||||
|
||||
String getUsername() { |
||||
return this.username; |
||||
} |
||||
|
||||
String getPassword() { |
||||
return this.password; |
||||
} |
||||
|
||||
String getDatabase() { |
||||
return this.database; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,81 @@
@@ -0,0 +1,81 @@
|
||||
/* |
||||
* Copyright 2012-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.docker.compose.service.connection.clickhouse; |
||||
|
||||
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; |
||||
import org.springframework.boot.docker.compose.core.RunningService; |
||||
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; |
||||
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; |
||||
import org.springframework.boot.docker.compose.service.connection.jdbc.JdbcUrlBuilder; |
||||
|
||||
/** |
||||
* {@link DockerComposeConnectionDetailsFactory} to create {@link JdbcConnectionDetails} |
||||
* for a {@code clickhouse} service. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class ClickHouseJdbcDockerComposeConnectionDetailsFactory |
||||
extends DockerComposeConnectionDetailsFactory<JdbcConnectionDetails> { |
||||
|
||||
private static final String[] CLICKHOUSE_CONTAINER_NAMES = { "clickhouse/clickhouse-server", "bitnami/clickhouse" }; |
||||
|
||||
protected ClickHouseJdbcDockerComposeConnectionDetailsFactory() { |
||||
super(CLICKHOUSE_CONTAINER_NAMES); |
||||
} |
||||
|
||||
@Override |
||||
protected JdbcConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { |
||||
return new ClickhouseJdbcDockerComposeConnectionDetails(source.getRunningService()); |
||||
} |
||||
|
||||
/** |
||||
* {@link JdbcConnectionDetails} backed by a {@code clickhouse} |
||||
* {@link RunningService}. |
||||
*/ |
||||
static class ClickhouseJdbcDockerComposeConnectionDetails extends DockerComposeConnectionDetails |
||||
implements JdbcConnectionDetails { |
||||
|
||||
private static final JdbcUrlBuilder jdbcUrlBuilder = new JdbcUrlBuilder("clickhouse", 8123); |
||||
|
||||
private final ClickHouseEnvironment environment; |
||||
|
||||
private final String jdbcUrl; |
||||
|
||||
ClickhouseJdbcDockerComposeConnectionDetails(RunningService service) { |
||||
super(service); |
||||
this.environment = new ClickHouseEnvironment(service.env()); |
||||
this.jdbcUrl = jdbcUrlBuilder.build(service, this.environment.getDatabase()); |
||||
} |
||||
|
||||
@Override |
||||
public String getUsername() { |
||||
return this.environment.getUsername(); |
||||
} |
||||
|
||||
@Override |
||||
public String getPassword() { |
||||
return this.environment.getPassword(); |
||||
} |
||||
|
||||
@Override |
||||
public String getJdbcUrl() { |
||||
return this.jdbcUrl; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
/* |
||||
* Copyright 2012-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.docker.compose.service.connection.clickhouse; |
||||
|
||||
import io.r2dbc.spi.ConnectionFactoryOptions; |
||||
|
||||
import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; |
||||
import org.springframework.boot.docker.compose.core.RunningService; |
||||
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; |
||||
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; |
||||
import org.springframework.boot.docker.compose.service.connection.r2dbc.ConnectionFactoryOptionsBuilder; |
||||
|
||||
/** |
||||
* {@link DockerComposeConnectionDetailsFactory} to create {@link R2dbcConnectionDetails} |
||||
* for a {@code clickhouse} service. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class ClickHouseR2dbcDockerComposeConnectionDetailsFactory |
||||
extends DockerComposeConnectionDetailsFactory<R2dbcConnectionDetails> { |
||||
|
||||
private static final String[] CLICKHOUSE_CONTAINER_NAMES = { "clickhouse/clickhouse-server", "bitnami/clickhouse" }; |
||||
|
||||
ClickHouseR2dbcDockerComposeConnectionDetailsFactory() { |
||||
super(CLICKHOUSE_CONTAINER_NAMES, "io.r2dbc.spi.ConnectionFactoryOptions"); |
||||
} |
||||
|
||||
@Override |
||||
protected R2dbcConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { |
||||
return new ClickhouseDbR2dbcDockerComposeConnectionDetails(source.getRunningService()); |
||||
} |
||||
|
||||
/** |
||||
* {@link R2dbcConnectionDetails} backed by a {@code clickhouse} |
||||
* {@link RunningService}. |
||||
*/ |
||||
static class ClickhouseDbR2dbcDockerComposeConnectionDetails extends DockerComposeConnectionDetails |
||||
implements R2dbcConnectionDetails { |
||||
|
||||
private static final ConnectionFactoryOptionsBuilder connectionFactoryOptionsBuilder = new ConnectionFactoryOptionsBuilder( |
||||
"clickhouse", 8123); |
||||
|
||||
private final ConnectionFactoryOptions connectionFactoryOptions; |
||||
|
||||
ClickhouseDbR2dbcDockerComposeConnectionDetails(RunningService service) { |
||||
super(service); |
||||
ClickHouseEnvironment environment = new ClickHouseEnvironment(service.env()); |
||||
this.connectionFactoryOptions = connectionFactoryOptionsBuilder.build(service, environment.getDatabase(), |
||||
environment.getUsername(), environment.getPassword()); |
||||
} |
||||
|
||||
@Override |
||||
public ConnectionFactoryOptions getConnectionFactoryOptions() { |
||||
return this.connectionFactoryOptions; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
/* |
||||
* Copyright 2012-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
/** |
||||
* Auto-configuration for Docker Compose ClickHouse service connections. |
||||
*/ |
||||
package org.springframework.boot.docker.compose.service.connection.clickhouse; |
||||
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
/* |
||||
* Copyright 2012-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.docker.compose.service.connection.clickhouse; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.Map; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
|
||||
/** |
||||
* Tests for {@link ClickHouseEnvironment}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class ClickHouseEnvironmentTests { |
||||
|
||||
@Test |
||||
void createWhenNoPasswordThrowsException() { |
||||
assertThatIllegalStateException().isThrownBy(() -> new ClickHouseEnvironment(Collections.emptyMap())) |
||||
.withMessage("No ClickHouse password found"); |
||||
} |
||||
|
||||
@Test |
||||
void getPasswordWhenHasPassword() { |
||||
ClickHouseEnvironment environment = new ClickHouseEnvironment(Map.of("CLICKHOUSE_PASSWORD", "secret")); |
||||
assertThat(environment.getPassword()).isEqualTo("secret"); |
||||
} |
||||
|
||||
@Test |
||||
void getPasswordWhenHasNoPasswordAndAllowEmptyPassword() { |
||||
ClickHouseEnvironment environment = new ClickHouseEnvironment(Map.of("ALLOW_EMPTY_PASSWORD", "true")); |
||||
assertThat(environment.getPassword()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
void getPasswordWhenHasNoPasswordAndAllowEmptyPasswordIsFalse() { |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> new ClickHouseEnvironment(Map.of("ALLOW_EMPTY_PASSWORD", "false"))) |
||||
.withMessage("No ClickHouse password found"); |
||||
} |
||||
|
||||
@Test |
||||
void getUsernameWhenNoUser() { |
||||
ClickHouseEnvironment environment = new ClickHouseEnvironment(Map.of("CLICKHOUSE_PASSWORD", "secret")); |
||||
assertThat(environment.getUsername()).isEqualTo("default"); |
||||
} |
||||
|
||||
@Test |
||||
void getUsernameWhenHasUser() { |
||||
ClickHouseEnvironment environment = new ClickHouseEnvironment( |
||||
Map.of("CLICKHOUSE_USER", "me", "CLICKHOUSE_PASSWORD", "secret")); |
||||
assertThat(environment.getUsername()).isEqualTo("me"); |
||||
} |
||||
|
||||
@Test |
||||
void getDatabaseWhenNoDatabase() { |
||||
ClickHouseEnvironment environment = new ClickHouseEnvironment(Map.of("CLICKHOUSE_PASSWORD", "secret")); |
||||
assertThat(environment.getDatabase()).isEqualTo("default"); |
||||
} |
||||
|
||||
@Test |
||||
void getDatabaseWhenHasDatabase() { |
||||
ClickHouseEnvironment environment = new ClickHouseEnvironment( |
||||
Map.of("CLICKHOUSE_DB", "db", "CLICKHOUSE_PASSWORD", "secret")); |
||||
assertThat(environment.getDatabase()).isEqualTo("db"); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue