diff --git a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/MavenModuleProfileTest.java b/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/MavenModuleProfileTest.java index 41547a9a..f51f6187 100644 --- a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/MavenModuleProfileTest.java +++ b/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/MavenModuleProfileTest.java @@ -98,10 +98,23 @@ class MavenModuleProfileTest { .doesNotContain("Spring Boot 3.4.x"); } + @Test + void duckDbDriverDoesNotLeakFromCompatibilityStoreIntoProductionApps() throws Exception { + Document eventFileStorePom = modulePom("modules/sinks/event-file-store/pom.xml"); + + assertThat(dependencyOptional(eventFileStorePom, "org.duckdb", "duckdb_jdbc")) + .as("event-file-store is a compatibility path; DuckDB must not be transitive") + .isTrue(); + } + private static Document rootPom() throws Exception { + return modulePom("pom.xml"); + } + + private static Document modulePom(String path) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - return factory.newDocumentBuilder().parse(Files.newInputStream(repositoryRoot().resolve("pom.xml"))); + return factory.newDocumentBuilder().parse(Files.newInputStream(repositoryRoot().resolve(path))); } private static String projectProperty(Document pom, String name) { @@ -150,6 +163,29 @@ class MavenModuleProfileTest { return List.copyOf(out); } + private static boolean dependencyOptional(Document pom, String groupId, String artifactId) { + Element dependencies = firstDirectChild(pom.getDocumentElement(), "dependencies"); + if (dependencies == null) { + return false; + } + NodeList children = dependencies.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + if (!(children.item(i) instanceof Element dependency) || !"dependency".equals(dependency.getTagName())) { + continue; + } + Element currentGroupId = firstDirectChild(dependency, "groupId"); + Element currentArtifactId = firstDirectChild(dependency, "artifactId"); + if (currentGroupId != null + && currentArtifactId != null + && groupId.equals(currentGroupId.getTextContent().trim()) + && artifactId.equals(currentArtifactId.getTextContent().trim())) { + Element optional = firstDirectChild(dependency, "optional"); + return optional != null && Boolean.parseBoolean(optional.getTextContent().trim()); + } + } + return false; + } + private static Element firstDirectChild(Element parent, String tagName) { NodeList children = parent.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { diff --git a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepository.java b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepository.java index c28852c0..27bae562 100644 --- a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepository.java +++ b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepository.java @@ -57,8 +57,7 @@ public final class JdbcVehicleStatMetricRepository implements VehicleStatReposit String normalizedVin = clean(vin); Date date = Date.valueOf(statDate); String strategy = DailyMileageStrategy.JT808_TOTAL_MILEAGE_DIFF.name(); - DailyMileageState existingState = findDailyMileageState(normalizedVin, date) - .orElseGet(() -> legacyDailyMileageState(normalizedVin, date).orElse(null)); + DailyMileageState existingState = findDailyMileageState(normalizedVin, date).orElse(null); double firstTotalMileage = existingState == null || !Double.isFinite(existingState.firstTotalMileageKm()) ? totalMileageKm : existingState.firstTotalMileageKm(); @@ -90,29 +89,6 @@ public final class JdbcVehicleStatMetricRepository implements VehicleStatReposit .findFirst(); } - private Optional legacyDailyMileageState(String vin, Date statDate) { - OptionalDouble start = findMetric(vin, statDate, DAILY_MILEAGE_START_TOTAL_KEY); - OptionalDouble latest = findMetric(vin, statDate, DAILY_MILEAGE_LATEST_TOTAL_KEY); - if (start.isEmpty() || latest.isEmpty()) { - return Optional.empty(); - } - return Optional.of(new DailyMileageState(start.getAsDouble(), latest.getAsDouble())); - } - - private OptionalDouble findMetric(String vin, Date statDate, String metricKey) { - List rows = jdbcTemplate.query(""" - SELECT metric_value - FROM vehicle_stat_metric - WHERE vin = ? AND stat_date = ? AND metric_key = ? - """, (rs, rowNum) -> rs.getBigDecimal("metric_value") == null - ? Double.NaN - : rs.getBigDecimal("metric_value").doubleValue(), vin, statDate, metricKey); - if (rows.isEmpty() || !Double.isFinite(rows.getFirst())) { - return OptionalDouble.empty(); - } - return OptionalDouble.of(rows.getFirst()); - } - private void upsertDailyMileageMetric(String vin, Date statDate, double value, String unit, String strategy, double firstTotalMileageKm, double latestTotalMileageKm) { try { diff --git a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepositoryTest.java b/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepositoryTest.java index d233bc03..6c0a08d8 100644 --- a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepositoryTest.java +++ b/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepositoryTest.java @@ -115,6 +115,37 @@ class JdbcVehicleStatMetricRepositoryTest { """, Double.class)).isEqualTo(9.5); } + @Test + void ignoresLegacySplitMileageStateMetricsWhenRecordingNewGpsTotalMileage() { + JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource( + "jdbc:h2:mem:vehicle_stat_metric_ignore_legacy_state;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1", + "sa", + "")); + JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate); + jdbcTemplate.update(""" + INSERT INTO vehicle_stat_metric + (vin, stat_date, metric_key, metric_value, metric_unit, calculation_method) + VALUES + ('VIN001', DATE '2026-07-01', 'daily_mileage_start_total_km', 100.0, 'km', 'JT808_TOTAL_MILEAGE_DIFF'), + ('VIN001', DATE '2026-07-01', 'daily_mileage_latest_total_km', 105.0, 'km', 'JT808_TOTAL_MILEAGE_DIFF') + """); + + VehicleDailyStatResult result = repository.recordDailyMileageSample( + "VIN001", LocalDate.of(2026, 7, 1), 110.0).orElseThrow(); + + assertThat(result.dailyMileageKm()).hasValue(0.0); + assertThat(jdbcTemplate.queryForObject(""" + SELECT first_total_mileage_km + FROM vehicle_stat_metric + WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km' + """, Double.class)).isEqualTo(110.0); + assertThat(jdbcTemplate.queryForObject(""" + SELECT latest_total_mileage_km + FROM vehicle_stat_metric + WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km' + """, Double.class)).isEqualTo(110.0); + } + @Test void readsLegacyCalculationMethodAsTotalMileageDifference() { JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource( diff --git a/modules/sinks/event-file-store/pom.xml b/modules/sinks/event-file-store/pom.xml index a89261d8..a8a21334 100644 --- a/modules/sinks/event-file-store/pom.xml +++ b/modules/sinks/event-file-store/pom.xml @@ -32,6 +32,7 @@ org.duckdb duckdb_jdbc + true com.fasterxml.jackson.core