Server fully owns ClickHouse schema — create database + tables on startup
All checks were successful
CI / build (push) Successful in 48s
CI / docker (push) Successful in 39s
CI / deploy (push) Successful in 9s

ClickHouseConfig.ensureDatabaseExists() connects without the database path
to run CREATE DATABASE IF NOT EXISTS before the main DataSource is used.
Removes the ConfigMap-based init scripts from the K8s manifest — the server
is now the single owner of all ClickHouse schema management.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-12 21:41:35 +01:00
parent f7ed91ef9c
commit 48bdb46760
2 changed files with 43 additions and 90 deletions

View File

@@ -2,6 +2,7 @@ package com.cameleer3.server.app.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
@@ -10,6 +11,9 @@ import org.springframework.jdbc.core.JdbcTemplate;
import jakarta.annotation.PostConstruct;
import javax.sql.DataSource;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
/**
* ClickHouse configuration.
@@ -25,6 +29,15 @@ public class ClickHouseConfig {
private final DataSource dataSource;
@Value("${spring.datasource.url}")
private String datasourceUrl;
@Value("${spring.datasource.username}")
private String datasourceUsername;
@Value("${spring.datasource.password}")
private String datasourcePassword;
public ClickHouseConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@@ -36,6 +49,7 @@ public class ClickHouseConfig {
@PostConstruct
void initSchema() {
ensureDatabaseExists();
var jdbc = new JdbcTemplate(dataSource);
for (String schemaFile : SCHEMA_FILES) {
try {
@@ -53,4 +67,33 @@ public class ClickHouseConfig {
}
}
}
/**
* Creates the ClickHouse database if it doesn't exist.
* Uses a separate connection without the database path, since the main
* DataSource connection fails if the database doesn't exist yet.
*/
private void ensureDatabaseExists() {
// Extract database name from URL: jdbc:ch://host:port/dbname -> dbname
// Strip the database path to connect at root level
String rootUrl = datasourceUrl.replaceFirst("/[^/?]+($|\\?)", "$1");
String dbName = extractDatabaseName(datasourceUrl);
try (Connection conn = DriverManager.getConnection(rootUrl, datasourceUsername, datasourcePassword);
Statement stmt = conn.createStatement()) {
stmt.execute("CREATE DATABASE IF NOT EXISTS " + dbName);
log.info("Ensured database '{}' exists", dbName);
} catch (Exception e) {
log.error("Failed to ensure database exists", e);
throw new RuntimeException("Database creation failed", e);
}
}
static String extractDatabaseName(String jdbcUrl) {
// jdbc:ch://host:port/dbname or jdbc:ch://host:port/dbname?params
String afterScheme = jdbcUrl.substring(jdbcUrl.indexOf("://") + 3);
String afterHost = afterScheme.substring(afterScheme.indexOf('/') + 1);
int paramIdx = afterHost.indexOf('?');
return paramIdx >= 0 ? afterHost.substring(0, paramIdx) : afterHost;
}
}