Revert to JdbcTemplate for schema init, keep comment-stripping fix
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:
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user