Diagnostics.java
package li.selman.persistencetest.assertions;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import li.selman.persistencetest.analysis.QueryAnalyzer;
import li.selman.persistencetest.analysis.QueryStatistics;
import li.selman.persistencetest.analysis.RepeatedQueryShape;
import li.selman.persistencetest.core.BindParameter;
import li.selman.persistencetest.core.CapturedQuery;
import li.selman.persistencetest.core.StatementType;
/**
* Formats captured queries into the failure-message text every {@code QueriesAssert}/
* {@code SingleCapturedQueryAssert} assertion uses - a single place responsible for making failures
* immediately explain themselves, per the library's diagnostics goal: execution order, normalized SQL,
* parameter samples, and summary statistics, not just "expected 2 but was 3".
*/
final class Diagnostics {
private Diagnostics() {}
static String describeQuery(CapturedQuery query) {
String params = query.parameters().isEmpty() ? "" : " -- params: " + describeParameters(query.parameters());
long millis = query.duration() == null ? 0 : query.duration().toMillis();
return "#%d [%s] %s%s (%dms)"
.formatted(query.sequence(), query.statementType(), query.normalizedSql(), params, millis);
}
static String describeParameters(List<BindParameter> parameters) {
return parameters.stream()
.map(parameter -> String.valueOf(parameter.value()))
.collect(Collectors.joining(", ", "[", "]"));
}
static String describeTimeline(List<CapturedQuery> queries) {
if (queries.isEmpty()) {
return " (none)";
}
return QueryAnalyzer.timelineOf(queries).stream()
.map(query -> " " + describeQuery(query))
.collect(Collectors.joining(System.lineSeparator()));
}
static String describeStatistics(List<CapturedQuery> queries) {
QueryStatistics stats = QueryAnalyzer.statisticsOf(queries);
return "total=%d, select=%d, insert=%d, update=%d, delete=%d, ddl=%d, other=%d, tables=%s"
.formatted(
stats.totalCount(),
stats.countOf(StatementType.SELECT),
stats.countOf(StatementType.INSERT),
stats.countOf(StatementType.UPDATE),
stats.countOf(StatementType.DELETE),
stats.countOf(StatementType.DDL),
stats.countOf(StatementType.OTHER),
stats.accessedTables());
}
static String describeRepeatedShapes(List<RepeatedQueryShape> shapes) {
return shapes.stream()
.map(shape -> " executed %d times: %s%n%s"
.formatted(
shape.occurrenceCount(),
shape.normalizedSql(),
shape.occurrences().stream()
.map(query -> " params: " + describeParameters(query.parameters()))
.collect(Collectors.joining(System.lineSeparator()))))
.collect(Collectors.joining(System.lineSeparator()));
}
static String withContext(String message, List<CapturedQuery> queries) {
return "%s%n%nSummary: %s%n%nCaptured queries (%d total):%n%s"
.formatted(message, describeStatistics(queries), queries.size(), describeTimeline(queries));
}
static String lowercase(String value) {
return value.toLowerCase(Locale.ROOT);
}
}