Skip to content

Commit 63be044

Browse files
Normalize table usage reports (#11605)
* Add `Sources` column to JavaScript markdown report * Add support for `GENERATE_TABLE_USAGE` env variable in Java report * Bump Node.js version in Gradle to match Dockerfile * Change report folder to `build/reports/tableusage` * Fix spotless git hook not registering * Move report classes to `org.hiero.mirror.common.tableusage` package * Normalize report formats * Rewrite Java CSV report to use Jackson's `CsvMapper` Signed-off-by: Steven Sheehy <[email protected]>
1 parent 4f659ee commit 63be044

File tree

24 files changed

+184
-209
lines changed

24 files changed

+184
-209
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ extra.apply {
2121
set("jooq.version", "3.20.5") // Must match buildSrc/build.gradle.kts
2222
set("prometheus-client.version", "1.3.10") // Temporary until next Spring Boot
2323
set("mapStructVersion", "1.6.3")
24-
set("nodeJsVersion", "22.14.0")
24+
set("nodeJsVersion", "22.17.1")
2525
set("protobufVersion", "4.31.1")
2626
set("reactorGrpcVersion", "1.2.4")
2727
set("tomcat.version", "10.1.43") // Temporary until next Spring Boot

buildSrc/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,4 @@ val gitHook =
4343
commandLine("git", "config", "core.hookspath", "buildSrc/src/main/resources/hooks")
4444
}
4545

46-
project.tasks.build { dependsOn(gitHook) }
46+
tasks.processResources { dependsOn(gitHook) }

common/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ dependencies {
88
val testClasses by configurations.registering
99
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
1010
api("com.fasterxml.jackson.core:jackson-databind")
11+
api("com.fasterxml.jackson.dataformat:jackson-dataformat-csv")
1112
api("com.github.ben-manes.caffeine:caffeine")
1213
api("com.google.guava:guava")
1314
api("com.google.protobuf:protobuf-java")

common/src/test/java/org/hiero/mirror/common/GlobalTestSetup.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
package org.hiero.mirror.common;
44

5-
import org.hiero.mirror.common.util.CsvGenerator;
6-
import org.hiero.mirror.common.util.MarkdownReportGenerator;
7-
import org.hiero.mirror.common.util.TestExecutionTracker;
5+
import org.hiero.mirror.common.tableusage.CsvReportGenerator;
6+
import org.hiero.mirror.common.tableusage.MarkdownReportGenerator;
7+
import org.hiero.mirror.common.tableusage.TestExecutionTracker;
88
import org.junit.platform.engine.TestExecutionResult;
99
import org.junit.platform.launcher.LauncherSession;
1010
import org.junit.platform.launcher.LauncherSessionListener;
@@ -48,7 +48,11 @@ public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult
4848

4949
@Override
5050
public void launcherSessionClosed(LauncherSession session) {
51-
MarkdownReportGenerator.generateTableUsageReport();
52-
CsvGenerator.generateTableUsageReport();
51+
final var generateTableUsage = System.getenv().getOrDefault("GENERATE_TABLE_USAGE", "true");
52+
53+
if (Boolean.parseBoolean(generateTableUsage)) {
54+
MarkdownReportGenerator.generateReport();
55+
CsvReportGenerator.generateReport();
56+
}
5357
}
5458
}

common/src/test/java/org/hiero/mirror/common/config/TableUsageReportTestConfiguration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import jakarta.persistence.EntityManager;
66
import org.hiero.mirror.common.filter.ApiTrackingFilter;
7+
import org.hiero.mirror.common.tableusage.TrackingRepositoryFactoryBean;
8+
import org.hiero.mirror.common.tableusage.TrackingRepositoryProxyPostProcessor;
79
import org.springframework.boot.test.context.TestConfiguration;
810
import org.springframework.context.annotation.Bean;
911
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

common/src/test/java/org/hiero/mirror/common/filter/ApiTrackingFilter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
import jakarta.servlet.ServletResponse;
1010
import jakarta.servlet.http.HttpServletRequest;
1111
import java.io.IOException;
12-
import org.hiero.mirror.common.util.EndpointContext;
13-
import org.hiero.mirror.common.util.EndpointNormalizer;
12+
import org.hiero.mirror.common.tableusage.EndpointContext;
13+
import org.hiero.mirror.common.tableusage.EndpointNormalizer;
1414

1515
public final class ApiTrackingFilter implements Filter {
1616

common/src/test/java/org/hiero/mirror/common/interceptor/RepositoryUsageInterceptor.java

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
package org.hiero.mirror.common.interceptor;
44

5-
import static org.hiero.mirror.common.util.EndpointContext.UNKNOWN_ENDPOINT;
5+
import static org.hiero.mirror.common.tableusage.EndpointContext.UNKNOWN_ENDPOINT;
66

77
import jakarta.persistence.EntityManager;
88
import jakarta.persistence.metamodel.EntityType;
@@ -15,31 +15,31 @@
1515
import org.aopalliance.intercept.MethodInterceptor;
1616
import org.aopalliance.intercept.MethodInvocation;
1717
import org.hibernate.query.spi.QueryImplementor;
18+
import org.hiero.mirror.common.tableusage.EndpointContext;
19+
import org.hiero.mirror.common.tableusage.SqlParsingUtil;
20+
import org.hiero.mirror.common.tableusage.TestExecutionTracker;
1821
import org.hiero.mirror.common.util.DomainUtils;
19-
import org.hiero.mirror.common.util.EndpointContext;
20-
import org.hiero.mirror.common.util.SqlParsingUtil;
21-
import org.hiero.mirror.common.util.TestExecutionTracker;
2222
import org.springframework.data.jpa.repository.Query;
2323
import org.springframework.data.repository.core.RepositoryInformation;
2424

2525
@CustomLog
2626
@RequiredArgsConstructor
2727
public class RepositoryUsageInterceptor implements MethodInterceptor {
28+
2829
private static final Map<String, Map<String, Set<String>>> API_TABLE_QUERIES = new ConcurrentHashMap<>();
2930

31+
private final RepositoryInformation repositoryInformation;
32+
private final EntityManager entityManager;
33+
3034
public static Map<String, Map<String, Set<String>>> getApiTableQueries() {
3135
return API_TABLE_QUERIES;
3236
}
3337

34-
private final RepositoryInformation repositoryInformation;
35-
private final EntityManager entityManager;
36-
3738
/**
38-
* Intercepts repository method invocation to track accessed database tables per API endpoint.
39-
* Only tracks during test execution as determined by {@link TestExecutionTracker}.
40-
* Extracts SQL queries either from native {@code @Query} annotations or Hibernate's query string,
41-
* then parses table names from the SQL.
42-
* Falls back to resolving the table name from the JPA entity metadata if no SQL found.
39+
* Intercepts repository method invocation to track accessed database tables per API endpoint. Only tracks during
40+
* test execution as determined by {@link TestExecutionTracker}. Extracts SQL queries either from native
41+
* {@code @Query} annotations or Hibernate's query string, then parses table names from the SQL. Falls back to
42+
* resolving the table name from the JPA entity metadata if no SQL found.
4343
*
4444
* @param invocation the method invocation context
4545
* @return the method invocation's original return value
@@ -83,8 +83,8 @@ public Object invoke(final MethodInvocation invocation) throws Throwable {
8383
}
8484

8585
/**
86-
* Resolves the JPA table name for the given entity class by querying the JPA metamodel.
87-
* Converts the entity name to snake_case.
86+
* Resolves the JPA table name for the given entity class by querying the JPA metamodel. Converts the entity name to
87+
* snake_case.
8888
*
8989
* @param entityClass the JPA entity class
9090
* @return the resolved table name, or {@code "UNKNOWN_TABLE"} if not found
@@ -113,10 +113,9 @@ private String extractSqlFromQueryAnnotation(final Method method) {
113113
}
114114

115115
/**
116-
* Attempts to extract the SQL query string executed by the repository method invocation.
117-
* First tries to get native SQL from {@link Query} annotation,
118-
* then attempts to invoke the method and check if it returns a Hibernate {@link QueryImplementor}
119-
* to obtain the query string.
116+
* Attempts to extract the SQL query string executed by the repository method invocation. First tries to get native
117+
* SQL from {@link Query} annotation, then attempts to invoke the method and check if it returns a Hibernate
118+
* {@link QueryImplementor} to obtain the query string.
120119
*
121120
* @param invocation the method invocation context
122121
* @return the SQL query string if available, or {@code null} if not extractable
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package org.hiero.mirror.common.tableusage;
4+
5+
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
6+
import java.io.IOException;
7+
import java.io.UncheckedIOException;
8+
import java.nio.file.Files;
9+
import java.nio.file.Paths;
10+
import lombok.experimental.UtilityClass;
11+
import org.hiero.mirror.common.interceptor.RepositoryUsageInterceptor;
12+
13+
@UtilityClass
14+
public final class CsvReportGenerator {
15+
16+
public static void generateReport() {
17+
final var path = Paths.get("build", "reports", "tableusage", "table-usage.csv");
18+
final var usageMap = RepositoryUsageInterceptor.getApiTableQueries();
19+
final var csvMapper = CsvMapper.builder().build();
20+
final var schema = csvMapper.schemaFor(TableUsage.class).withHeader().sortedBy("endpoint", "table", "source");
21+
path.getParent().toFile().mkdirs();
22+
23+
try (final var writer = Files.newBufferedWriter(path)) {
24+
var sequenceWriter =
25+
csvMapper.writerFor(TableUsage.class).with(schema).writeValues(writer);
26+
27+
for (final var entry : usageMap.entrySet()) {
28+
final var endpoint = entry.getKey();
29+
30+
for (final var tableEntry : entry.getValue().entrySet()) {
31+
final var table = tableEntry.getKey();
32+
33+
for (final var source : tableEntry.getValue()) {
34+
sequenceWriter.write(new TableUsage(endpoint, table, source));
35+
}
36+
}
37+
}
38+
} catch (final IOException e) {
39+
throw new UncheckedIOException(e);
40+
}
41+
}
42+
43+
private record TableUsage(String endpoint, String table, String source) {}
44+
}

common/src/test/java/org/hiero/mirror/common/util/EndpointContext.java renamed to common/src/test/java/org/hiero/mirror/common/tableusage/EndpointContext.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
// SPDX-License-Identifier: Apache-2.0
22

3-
package org.hiero.mirror.common.util;
3+
package org.hiero.mirror.common.tableusage;
44

55
import lombok.experimental.UtilityClass;
66

77
/**
88
* Provides per-thread tracking of the current API endpoint context.
99
* <p>
10-
* This class stores the logical endpoint (e.g., <code>/graphql</code>, <code>/importer</code>) associated
11-
* with the currently executing request or test, allowing downstream components such as interceptors,
12-
* instrumentation, or logging to access a consistent identifier for attribution purposes.
10+
* This class stores the logical endpoint (e.g., <code>/graphql</code>, <code>/importer</code>) associated with the
11+
* currently executing request or test, allowing downstream components such as interceptors, instrumentation, or logging
12+
* to access a consistent identifier for attribution purposes.
1313
* </p>
1414
*/
1515
@UtilityClass
@@ -20,14 +20,14 @@ public class EndpointContext {
2020

2121
private static final ThreadLocal<String> CURRENT_ENDPOINT = new InheritableThreadLocal<>();
2222

23-
public static void setCurrentEndpoint(final String endpoint) {
24-
CURRENT_ENDPOINT.set(endpoint);
25-
}
26-
2723
public static String getCurrentEndpoint() {
2824
return CURRENT_ENDPOINT.get();
2925
}
3026

27+
public static void setCurrentEndpoint(final String endpoint) {
28+
CURRENT_ENDPOINT.set(endpoint);
29+
}
30+
3131
public static void clearCurrentEndpoint() {
3232
CURRENT_ENDPOINT.remove();
3333
}

common/src/test/java/org/hiero/mirror/common/util/EndpointNormalizer.java renamed to common/src/test/java/org/hiero/mirror/common/tableusage/EndpointNormalizer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: Apache-2.0
22

3-
package org.hiero.mirror.common.util;
3+
package org.hiero.mirror.common.tableusage;
44

55
import java.util.Map;
66
import java.util.regex.Pattern;

0 commit comments

Comments
 (0)