Fix schema init: bypass DataSource, use direct JDBC with qualified table names
The auto-configured DataSource targets jdbc:ch://.../cameleer3 which fails if the database doesn't exist yet. Schema init now uses a direct JDBC connection to the root URL, creates the database first, then applies all schema SQL with fully qualified cameleer3.* table names. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,10 @@ import java.sql.Statement;
|
||||
* <p>
|
||||
* Spring Boot auto-configures the DataSource from {@code spring.datasource.*} properties.
|
||||
* This class exposes a JdbcTemplate bean and initializes the schema on startup.
|
||||
* <p>
|
||||
* 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.
|
||||
*/
|
||||
@Configuration
|
||||
public class ClickHouseConfig {
|
||||
@@ -49,43 +53,30 @@ public class ClickHouseConfig {
|
||||
|
||||
@PostConstruct
|
||||
void initSchema() {
|
||||
ensureDatabaseExists();
|
||||
var jdbc = new JdbcTemplate(dataSource);
|
||||
for (String schemaFile : SCHEMA_FILES) {
|
||||
try {
|
||||
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) {
|
||||
String sql = new ClassPathResource(schemaFile).getContentAsString(StandardCharsets.UTF_8);
|
||||
for (String statement : sql.split(";")) {
|
||||
String trimmed = statement.trim();
|
||||
if (!trimmed.isEmpty() && !trimmed.startsWith("--")) {
|
||||
jdbc.execute(trimmed);
|
||||
stmt.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
log.error("ClickHouse schema initialization failed", e);
|
||||
throw new RuntimeException("ClickHouse schema initialization failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
-- Cameleer3 ClickHouse Schema
|
||||
-- Tables for route executions, route diagrams, and agent metrics.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS route_executions (
|
||||
CREATE TABLE IF NOT EXISTS cameleer3.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 route_diagrams (
|
||||
CREATE TABLE IF NOT EXISTS cameleer3.route_diagrams (
|
||||
content_hash String,
|
||||
route_id LowCardinality(String),
|
||||
agent_id LowCardinality(String),
|
||||
@@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS route_diagrams (
|
||||
ENGINE = ReplacingMergeTree(created_at)
|
||||
ORDER BY (content_hash);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS agent_metrics (
|
||||
CREATE TABLE IF NOT EXISTS cameleer3.agent_metrics (
|
||||
agent_id LowCardinality(String),
|
||||
collected_at DateTime64(3, 'UTC'),
|
||||
metric_name LowCardinality(String),
|
||||
|
||||
@@ -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 route_executions
|
||||
ALTER TABLE cameleer3.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 route_executions
|
||||
ADD COLUMN IF NOT EXISTS diagram_content_hash String DEFAULT '';
|
||||
|
||||
-- Skip indexes for full-text search on new text columns
|
||||
ALTER TABLE route_executions
|
||||
ALTER TABLE cameleer3.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 route_executions
|
||||
ALTER TABLE cameleer3.route_executions
|
||||
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