diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/ClickHouseConfig.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/ClickHouseConfig.java index e0f66d6e..c31de99b 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/ClickHouseConfig.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/ClickHouseConfig.java @@ -2,7 +2,6 @@ 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; @@ -11,9 +10,7 @@ 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; +import java.util.stream.Collectors; /** * ClickHouse configuration. @@ -21,9 +18,8 @@ import java.sql.Statement; * Spring Boot auto-configures the DataSource from {@code spring.datasource.*} properties. * This class exposes a JdbcTemplate bean and initializes the schema on startup. *
- * Schema initialization uses a direct JDBC connection (bypassing the DataSource) - * to avoid chicken-and-egg problems: the DataSource targets a specific database - * that may not exist yet on a fresh ClickHouse instance. + * The ClickHouse container's {@code CLICKHOUSE_DB} env var creates the database; + * this class creates the tables within it. */ @Configuration public class ClickHouseConfig { @@ -33,15 +29,6 @@ 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; } @@ -53,42 +40,26 @@ public class ClickHouseConfig { @PostConstruct void initSchema() { - String dbName = extractDatabaseName(datasourceUrl); - String rootUrl = datasourceUrl.replaceFirst("/[^/?]+($|\\?)", "/$1"); - - try (Connection conn = DriverManager.getConnection(rootUrl, datasourceUsername, datasourcePassword); - Statement stmt = conn.createStatement()) { - - // Create the database first - stmt.execute("CREATE DATABASE IF NOT EXISTS " + dbName); - log.info("Ensured database '{}' exists", dbName); - - // Apply schema files using fully qualified table names - for (String schemaFile : SCHEMA_FILES) { + var jdbc = new JdbcTemplate(dataSource); + for (String schemaFile : SCHEMA_FILES) { + try { String sql = new ClassPathResource(schemaFile).getContentAsString(StandardCharsets.UTF_8); - // Strip comment lines before splitting into statements + // Strip comment lines before splitting — a statement preceded by + // comment lines would otherwise be skipped entirely. String stripped = sql.lines() .filter(line -> !line.trim().startsWith("--")) - .collect(java.util.stream.Collectors.joining("\n")); + .collect(Collectors.joining("\n")); for (String statement : stripped.split(";")) { String trimmed = statement.trim(); if (!trimmed.isEmpty()) { - stmt.execute(trimmed); + jdbc.execute(trimmed); } } log.info("Applied schema: {}", schemaFile); + } catch (Exception e) { + log.error("Failed to apply schema: {}", schemaFile, e); + throw new RuntimeException("Schema initialization failed: " + schemaFile, e); } - } catch (Exception e) { - log.error("ClickHouse schema initialization failed", e); - throw new RuntimeException("ClickHouse schema initialization 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; - } } diff --git a/cameleer3-server-app/src/main/resources/clickhouse/01-schema.sql b/cameleer3-server-app/src/main/resources/clickhouse/01-schema.sql index 3c2afd25..ab56da70 100644 --- a/cameleer3-server-app/src/main/resources/clickhouse/01-schema.sql +++ b/cameleer3-server-app/src/main/resources/clickhouse/01-schema.sql @@ -1,7 +1,7 @@ -- Cameleer3 ClickHouse Schema -- Tables for route executions, route diagrams, and agent metrics. -CREATE TABLE IF NOT EXISTS cameleer3.route_executions ( +CREATE TABLE IF NOT EXISTS route_executions ( execution_id String, route_id LowCardinality(String), agent_id LowCardinality(String), @@ -32,7 +32,7 @@ ORDER BY (agent_id, status, start_time, execution_id) TTL toDateTime(start_time) + toIntervalDay(30) SETTINGS ttl_only_drop_parts = 1; -CREATE TABLE IF NOT EXISTS cameleer3.route_diagrams ( +CREATE TABLE IF NOT EXISTS route_diagrams ( content_hash String, route_id LowCardinality(String), agent_id LowCardinality(String), @@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS cameleer3.route_diagrams ( ENGINE = ReplacingMergeTree(created_at) ORDER BY (content_hash); -CREATE TABLE IF NOT EXISTS cameleer3.agent_metrics ( +CREATE TABLE IF NOT EXISTS agent_metrics ( agent_id LowCardinality(String), collected_at DateTime64(3, 'UTC'), metric_name LowCardinality(String), diff --git a/cameleer3-server-app/src/main/resources/clickhouse/02-search-columns.sql b/cameleer3-server-app/src/main/resources/clickhouse/02-search-columns.sql index 024e9620..2b11b435 100644 --- a/cameleer3-server-app/src/main/resources/clickhouse/02-search-columns.sql +++ b/cameleer3-server-app/src/main/resources/clickhouse/02-search-columns.sql @@ -1,7 +1,7 @@ -- Phase 2: Schema extension for search, detail, and diagram linking columns. -- Adds exchange snapshot data, processor tree metadata, and diagram content hash. -ALTER TABLE cameleer3.route_executions +ALTER TABLE route_executions ADD COLUMN IF NOT EXISTS exchange_bodies String DEFAULT '', ADD COLUMN IF NOT EXISTS exchange_headers String DEFAULT '', ADD COLUMN IF NOT EXISTS processor_depths Array(UInt16) DEFAULT [], @@ -16,10 +16,10 @@ ALTER TABLE cameleer3.route_executions ADD COLUMN IF NOT EXISTS diagram_content_hash String DEFAULT ''; -- Skip indexes for full-text search on new text columns -ALTER TABLE cameleer3.route_executions +ALTER TABLE route_executions ADD INDEX IF NOT EXISTS idx_exchange_bodies exchange_bodies TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 4, ADD INDEX IF NOT EXISTS idx_exchange_headers exchange_headers TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 4; -- Skip index on error_stacktrace (not indexed in 01-schema.sql, needed for SRCH-05) -ALTER TABLE cameleer3.route_executions +ALTER TABLE route_executions ADD INDEX IF NOT EXISTS idx_error_stacktrace error_stacktrace TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 4;