diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriter.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriter.java index 4dd47fb9..63cea842 100644 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriter.java +++ b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriter.java @@ -20,6 +20,8 @@ public final class TdengineJdbcHistoryWriter implements TdengineHistoryWriter { private final DataSource dataSource; private final TdengineHistorySchema schema; private final TdengineHistoryStatements statements; + private final Object schemaInitializationMonitor = new Object(); + private volatile boolean schemaInitialized; public TdengineJdbcHistoryWriter(DataSource dataSource, TdengineHistorySchema schema) { if (dataSource == null) { @@ -41,6 +43,7 @@ public final class TdengineJdbcHistoryWriter implements TdengineHistoryWriter { } } }); + schemaInitialized = true; } @Override @@ -62,7 +65,29 @@ public final class TdengineJdbcHistoryWriter implements TdengineHistoryWriter { if (rows == null || rows.isEmpty()) { return; } + if (!schemaInitialized) { + synchronized (schemaInitializationMonitor) { + if (!schemaInitialized) { + append(rows, mapper, true); + schemaInitialized = true; + return; + } + } + } + append(rows, mapper, false); + } + + private void append(List rows, + Function mapper, + boolean initializeSchema) throws IOException { inTransaction(connection -> { + if (initializeSchema) { + try (Statement statement = connection.createStatement()) { + for (String sql : schema.bootstrapSql()) { + statement.execute(sql); + } + } + } Set createdChildTables = new LinkedHashSet<>(); Map preparedStatements = new LinkedHashMap<>(); try { diff --git a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriterTest.java b/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriterTest.java index 3d1a1dcf..37b49bfd 100644 --- a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriterTest.java +++ b/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriterTest.java @@ -41,6 +41,7 @@ class TdengineJdbcHistoryWriterTest { writer.appendRawFrames(List.of(first, second)); + assertThat(jdbc.executedSql).startsWith(schema.bootstrapSql().toArray(String[]::new)); assertThat(jdbc.executedSql) .filteredOn(sql -> sql.contains("USING raw_frames TAGS")) .hasSize(1); @@ -80,6 +81,7 @@ class TdengineJdbcHistoryWriterTest { writer.appendLocations(List.of(location)); + assertThat(jdbc.executedSql).startsWith(schema.bootstrapSql().toArray(String[]::new)); assertThat(jdbc.executedSql) .anyMatch(sql -> sql.contains("USING vehicle_locations TAGS")); RecordingJdbc.PreparedBatch batch = jdbc.preparedBatches.values().iterator().next(); @@ -96,6 +98,7 @@ class TdengineJdbcHistoryWriterTest { writer.appendTelemetryFields(List.of(first, second)); + assertThat(jdbc.executedSql).startsWith(schema.bootstrapSql().toArray(String[]::new)); assertThat(jdbc.executedSql) .filteredOn(sql -> sql.contains("USING telemetry_fields TAGS")) .hasSize(1); @@ -107,6 +110,20 @@ class TdengineJdbcHistoryWriterTest { assertThat(batch.rows().get(1).get(6)).isEqualTo("124.5"); } + @Test + void schemaBootstrapRunsOnlyOnceAcrossWrites() throws Exception { + RecordingJdbc jdbc = new RecordingJdbc(); + TdengineJdbcHistoryWriter writer = new TdengineJdbcHistoryWriter(jdbc.dataSource(), schema); + + writer.appendRawFrames(List.of(rawFrame("frame-1", Instant.parse("2026-06-29T05:00:01Z")))); + writer.appendRawFrames(List.of(rawFrame("frame-2", Instant.parse("2026-06-29T05:00:02Z")))); + + assertThat(jdbc.executedSql) + .filteredOn(sql -> sql.startsWith("CREATE DATABASE")) + .hasSize(1); + assertThat(jdbc.commits).isEqualTo(2); + } + private static TdengineRawFrameRow rawFrame(String frameId, Instant ts) { return new TdengineRawFrameRow( ts,