Architecture
Design principles
database-audits-core carries no dependency-injection annotations. Every class takes its dependencies as
constructor arguments, so any container (Spring, Guice, plain new) can wire it. The Spring integration lives
in database-audits-spring-boot, which wires everything with explicit @Bean methods.
Audits return findings; callers assert. Every audit() method returns a List<String> of human-readable
violation messages — empty when clean — and the calling test makes the assertions (typically with AssertJ). This
means AssertJ is test-scoped only, never a compile or runtime dependency of the library.
Never pass vacuously. A run that could not actually check anything throws rather than returning a
clean-looking empty list. Runtime audits throw IllegalStateException on an empty SQL capture and on a
wholly-unexplainable run; plan audits throw UnsupportedOperationException on a non-PostgreSQL platform.
Both errors indicate a configuration problem to fix, not a clean schema.
DatabasePlatform — the hub
DatabasePlatform is an enum of the four supported products: H2, MARIADB, MYSQL, POSTGRESQL. Every
place in the library that needs platform-specific SQL holds an exhaustive switch with no default branch:
return switch (platform) {
case POSTGRESQL -> POSTGRESQL_FK_SQL;
case MYSQL, MARIADB -> MYSQL_FK_SQL;
case H2 -> H2_FK_SQL;
};Adding a new enum value makes the compiler flag every SQL site that needs updating — a compile-time guarantee of
completeness. Detect the platform once at startup with DatabasePlatform.fromDataSource(DataSource), which
opens one connection, reads DatabaseMetaData.getDatabaseProductName(), and closes the connection.
Catalog family (all platforms)
Five audits that query database metadata — information_schema or pg_catalog — over plain JDBC and compare
it against rules. Results are deterministic regardless of test data; no SQL execution is analyzed.
CatalogQueries is the minimal JDBC-to-list-of-maps layer. It prepares one parameterized statement per call
and returns rows as Map<String, Object> with case-insensitive key lookup — necessary because PostgreSQL
lower-cases unquoted aliases while H2 upper-cases them.
IndexCatalog reads every index of a schema as IndexDefinition records (key columns in order, excluding
INCLUDE columns and full-text/spatial indexes). It is the shared building block for ForeignKeyIndexAudit
(leading-prefix coverage) and RedundantIndexAudit (prefix containment).
IndexDefinition is a value record holding a table name, index name, ordered column list, and flags for
unique, primary, partial, and expression/prefix columns.
Runtime family (PostgreSQL 16+ only)
Four audits that inspect the real SQL Hibernate executed during the test run. Three use EXPLAIN; one
token-scans for missing WHERE clauses.
SQL capture
SqlCapturingStatementInspector implements Hibernate’s StatementInspector, recording every statement with
JDBC ? placeholders (no bind values) — exactly what EXPLAIN (GENERIC_PLAN) needs. The same instance must
be both Hibernate’s inspector and the one the runtime audits read; see
Usage — Runtime audits for wiring.
Query plan analysis
QueryPlanExplainer runs EXPLAIN (GENERIC_PLAN, FORMAT JSON) with chosen planner-penalty settings (for
example SET enable_seqscan = off). A surviving node of the penalized kind proves no index can serve that
access path, without needing real bind values or test data. Requires preferQueryMode=simple on the JDBC URL —
generic-plan EXPLAIN only works over PostgreSQL’s simple query protocol.
Template method
CapturedSqlPlanAuditTemplate is the base class for the three EXPLAIN-based audits. The fixed algorithm
de-duplicates captured SQL by normalized statement shape, plans each candidate with penalties applied, and
collects offending nodes. Both vacuous-run guards — empty capture and wholly-unexplainable run — live here.
Subclasses supply which statements to EXPLAIN, which GUCs to penalize, and how to recognize an offending node.
UnconditionalMutationAudit is runtime but uses no EXPLAIN — it scans the capture for UPDATE/DELETE
without WHERE.
JPA family
SchemaEntityValidationAudit takes an EntityManagerFactory. When hibernate.hbm2ddl.auto=validate is
active, Hibernate validates every mapped entity at factory-build time; if the factory was built, validation
passed. The audit checks the factory is non-null as a formality confirming the paired test enabled validation.
Exclusions
Every audit accepts exclusion parameters so consumers suppress known-intentional violations rather than making
the audit guess. Exclusion types vary by audit — table names, constraint names, table.column identifiers,
index names, relation names, SQL fragments, or normalized statement strings. See
Audits for each audit’s exclusion type.

