Revert to JdbcTemplate for schema init, keep comment-stripping fix
All checks were successful
CI / build (push) Successful in 48s
CI / docker (push) Successful in 36s
CI / deploy (push) Successful in 9s

The DriverManager-based approach likely failed because the ClickHouse
JDBC driver wasn't registered with DriverManager. The original
JdbcTemplate approach worked for route_diagrams and agent_metrics —
only route_executions was skipped due to the comment-parsing bug.

Reverts to simple JdbcTemplate-based init with unqualified table names
(DataSource targets cameleer3 database). The CLICKHOUSE_DB env var on
the ClickHouse container handles database creation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-12 22:05:12 +01:00
parent a2cbd115ee
commit a44a0c970b
3 changed files with 19 additions and 48 deletions

View File

@@ -2,7 +2,6 @@ package com.cameleer3.server.app.config;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
@@ -11,9 +10,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.sql.Connection; import java.util.stream.Collectors;
import java.sql.DriverManager;
import java.sql.Statement;
/** /**
* ClickHouse configuration. * ClickHouse configuration.
@@ -21,9 +18,8 @@ import java.sql.Statement;
* Spring Boot auto-configures the DataSource from {@code spring.datasource.*} properties. * Spring Boot auto-configures the DataSource from {@code spring.datasource.*} properties.
* This class exposes a JdbcTemplate bean and initializes the schema on startup. * This class exposes a JdbcTemplate bean and initializes the schema on startup.
* <p> * <p>
* Schema initialization uses a direct JDBC connection (bypassing the DataSource) * The ClickHouse container's {@code CLICKHOUSE_DB} env var creates the database;
* to avoid chicken-and-egg problems: the DataSource targets a specific database * this class creates the tables within it.
* that may not exist yet on a fresh ClickHouse instance.
*/ */
@Configuration @Configuration
public class ClickHouseConfig { public class ClickHouseConfig {
@@ -33,15 +29,6 @@ public class ClickHouseConfig {
private final DataSource dataSource; 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) { public ClickHouseConfig(DataSource dataSource) {
this.dataSource = dataSource; this.dataSource = dataSource;
} }
@@ -53,42 +40,26 @@ public class ClickHouseConfig {
@PostConstruct @PostConstruct
void initSchema() { void initSchema() {
String dbName = extractDatabaseName(datasourceUrl); var jdbc = new JdbcTemplate(dataSource);
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) { for (String schemaFile : SCHEMA_FILES) {
try {
String sql = new ClassPathResource(schemaFile).getContentAsString(StandardCharsets.UTF_8); 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() String stripped = sql.lines()
.filter(line -> !line.trim().startsWith("--")) .filter(line -> !line.trim().startsWith("--"))
.collect(java.util.stream.Collectors.joining("\n")); .collect(Collectors.joining("\n"));
for (String statement : stripped.split(";")) { for (String statement : stripped.split(";")) {
String trimmed = statement.trim(); String trimmed = statement.trim();
if (!trimmed.isEmpty()) { if (!trimmed.isEmpty()) {
stmt.execute(trimmed); jdbc.execute(trimmed);
} }
} }
log.info("Applied schema: {}", schemaFile); log.info("Applied schema: {}", schemaFile);
}
} catch (Exception e) { } catch (Exception e) {
log.error("ClickHouse schema initialization failed", e); log.error("Failed to apply schema: {}", schemaFile, e);
throw new RuntimeException("ClickHouse schema initialization failed", e); throw new RuntimeException("Schema initialization failed: " + schemaFile, 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;
} }
} }

View File

@@ -1,7 +1,7 @@
-- Cameleer3 ClickHouse Schema -- Cameleer3 ClickHouse Schema
-- Tables for route executions, route diagrams, and agent metrics. -- 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, execution_id String,
route_id LowCardinality(String), route_id LowCardinality(String),
agent_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) TTL toDateTime(start_time) + toIntervalDay(30)
SETTINGS ttl_only_drop_parts = 1; SETTINGS ttl_only_drop_parts = 1;
CREATE TABLE IF NOT EXISTS cameleer3.route_diagrams ( CREATE TABLE IF NOT EXISTS route_diagrams (
content_hash String, content_hash String,
route_id LowCardinality(String), route_id LowCardinality(String),
agent_id LowCardinality(String), agent_id LowCardinality(String),
@@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS cameleer3.route_diagrams (
ENGINE = ReplacingMergeTree(created_at) ENGINE = ReplacingMergeTree(created_at)
ORDER BY (content_hash); ORDER BY (content_hash);
CREATE TABLE IF NOT EXISTS cameleer3.agent_metrics ( CREATE TABLE IF NOT EXISTS agent_metrics (
agent_id LowCardinality(String), agent_id LowCardinality(String),
collected_at DateTime64(3, 'UTC'), collected_at DateTime64(3, 'UTC'),
metric_name LowCardinality(String), metric_name LowCardinality(String),

View File

@@ -1,7 +1,7 @@
-- Phase 2: Schema extension for search, detail, and diagram linking columns. -- Phase 2: Schema extension for search, detail, and diagram linking columns.
-- Adds exchange snapshot data, processor tree metadata, and diagram content hash. -- 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_bodies String DEFAULT '',
ADD COLUMN IF NOT EXISTS exchange_headers String DEFAULT '', ADD COLUMN IF NOT EXISTS exchange_headers String DEFAULT '',
ADD COLUMN IF NOT EXISTS processor_depths Array(UInt16) 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 ''; ADD COLUMN IF NOT EXISTS diagram_content_hash String DEFAULT '';
-- Skip indexes for full-text search on new text columns -- 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_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; 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) -- 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; ADD INDEX IF NOT EXISTS idx_error_stacktrace error_stacktrace TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 4;