Skip to content

Commit 456f180

Browse files
committed
docs(site): Document the nine new audits and their exclusions
Adds each new audit's row to audits.adoc's per-family tables (DuplicateForeignKey/PrimaryKeyType/UniqueIndexNotNull in catalog; EagerCollectionFetch/MissingVersionAttribute/UnmappedDatabaseObject in JPA, renaming the "JPA audit" heading to plural; OffsetPagination/ RepeatedStatement/UnusedIndex in runtime, splitting the "token-scan" heading to plural and fixing usage.adoc's stale plan-based/token-scan audit lists) and each new DatabaseAuditExcludes builder method to exclusions.adoc's per-family reference tables, noting that PrimaryKeyTypeAuditAssertion and UnmappedDatabaseObjectAuditAssertion - unlike PrimaryKeyPresenceAuditAssertion - do not auto-exclude the Liquibase bookkeeping tables.
1 parent fd287af commit 456f180

3 files changed

Lines changed: 100 additions & 7 deletions

File tree

integration/src/site/asciidoc/audits.adoc

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ Catalog audits read database metadata (information schema / system catalogs). No
1414
| `PrimaryKeyPresenceAuditAssertion`
1515
| Table names (`Set<String>`). Liquibase bookkeeping tables are excluded automatically.
1616

17+
| `PrimaryKeyTypeAudit`
18+
| `PrimaryKeyTypeAuditAssertion`
19+
| Column names in `table.column` format (`Set<String>`)
20+
21+
| `DuplicateForeignKeyAudit`
22+
| `DuplicateForeignKeyAuditAssertion`
23+
| FK constraint names (`Set<String>`)
24+
1725
| `ForeignKeyIndexAudit`
1826
| `ForeignKeyIndexAuditAssertion`
1927
| FK constraint names (`Set<String>`)
@@ -29,6 +37,10 @@ Catalog audits read database metadata (information schema / system catalogs). No
2937
| `RedundantIndexAudit`
3038
| `RedundantIndexAuditAssertion`
3139
| Index names (`Set<String>`)
40+
41+
| `UniqueIndexNotNullAudit`
42+
| `UniqueIndexNotNullAuditAssertion`
43+
| Index names (`Set<String>`)
3244
|===
3345

3446
=== Catalog audit example
@@ -52,14 +64,26 @@ void assertForeignKeysIndexed() {
5264
}
5365
----
5466

55-
== JPA audit
67+
== JPA audits
5668

5769
|===
5870
| Audit | Assertion Bean | Exclusion type
5971

6072
| `SchemaEntityValidationAudit`
6173
| `SchemaEntityValidationAuditAssertion`
6274
| None. The audit walks Hibernate's entity mappings against the live schema and reports every mismatch (missing table, missing column, incompatible column type) in one run. Run under the default `ddl-auto=none`; do not set `ddl-auto=validate`, whose fail-fast startup check aborts the context on the first mismatch.
75+
76+
| `UnmappedDatabaseObjectAudit`
77+
| `UnmappedDatabaseObjectAuditAssertion`
78+
| Relations — a table or `table.column` name, optionally schema-qualified (`Set<String>`). The reverse of `SchemaEntityValidationAudit`: proves the schema holds only what is mapped. Exclude migration-tool bookkeeping tables (e.g. `databasechangelog`, `databasechangeloglock`).
79+
80+
| `MissingVersionAttributeAudit`
81+
| `MissingVersionAttributeAuditAssertion`
82+
| Entities — fully-qualified name, simple name, or physical table name (`Set<String>`)
83+
84+
| `EagerCollectionFetchAudit`
85+
| `EagerCollectionFetchAuditAssertion`
86+
| Collection roles, e.g. `com.acme.Order.items` (`Set<String>`)
6387
|===
6488

6589
=== JPA audit example
@@ -104,19 +128,34 @@ datasource JDBC URL. See link:usage.html#postgresql-jdbc-requirement[Usage — P
104128
| `JoinIndexAudit`
105129
| `JoinIndexAuditAssertion`
106130
| Relation names (`Set<String>`), SQL fragments (`List<String>`)
131+
132+
| `UnusedIndexAudit`
133+
| `UnusedIndexAuditAssertion`
134+
| Index names (`Set<String>`). Advisory and workload-dependent: the capture must hold a representative
135+
workload, and a generic plan can miss an index the planner would pick under production data. Always confirm
136+
against production `pg_stat_user_indexes` before dropping an index this reports.
107137
|===
108138

109-
=== Token-scan audit (all platforms)
139+
=== Token-scan audits (all platforms)
110140

111-
`UnconditionalMutationAudit` detects full-table `UPDATE`/`DELETE` via a token scan of the captured SQL — no
112-
`EXPLAIN` needed. It runs on every supported database platform.
141+
These detect via a token scan of the captured SQL — no `EXPLAIN` needed — so they run on every supported
142+
database platform.
113143

114144
|===
115145
| Audit | Assertion Bean | Exclusion types
116146

117147
| `UnconditionalMutationAudit`
118148
| `UnconditionalMutationAuditAssertion`
119149
| Exact statement strings (`Set<String>`)
150+
151+
| `OffsetPaginationAudit`
152+
| `OffsetPaginationAuditAssertion`
153+
| SQL fragments (`List<String>`)
154+
155+
| `RepeatedStatementAudit`
156+
| `RepeatedStatementAuditAssertion`
157+
| A threshold (`int`, minimum 2) plus SQL fragments (`List<String>`). Counts accumulate for the capturer's
158+
whole lifetime, so the threshold is a regression tripwire (a generous default like 50), not a precise N+1 count.
120159
|===
121160

122161
=== Runtime audit example

integration/src/site/asciidoc/exclusions.adoc

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,24 @@ or the fluent builder to name each exclusion:
2424
----
2525
DatabaseAuditExcludes excludes = DatabaseAuditExcludes.builder()
2626
.primaryKeyTables(Set.of("legacy_table_no_pk"))
27+
.primaryKeyTypeColumns(Set.of("lookup_values.id")) // table.column
28+
.duplicateForeignKeyConstraints(Set.of("fk_orders_customer_legacy"))
2729
.foreignKeyIndexConstraints(Set.of("fk_unindexed_by_design"))
2830
.foreignKeyNotNullColumns(Set.of("order.customer_id")) // table.column
2931
.foreignKeyTypeMatchColumns(Set.of("event.entity_id")) // table.column
3032
.redundantIndexes(Set.of("idx_status_redundant"))
33+
.uniqueIndexNotNullIndexes(Set.of("uq_orders_active_customer"))
3134
.jpaExcludedRelations(Set.of("legacy_reporting_view")) // table or table.column, case-insensitive
35+
.unmappedDatabaseObjectRelations(Set.of("databasechangelog", "databasechangeloglock"))
36+
.missingVersionEntities(Set.of("AuditLogEntry")) // fully-qualified name, simple name, or table
37+
.eagerCollectionRoles(Set.of("com.acme.Order.items"))
3238
.planRelations(Set.of("small_lookup_table")) // shared by WHERE/ORDER BY/JOIN audits
3339
.planSqlFragments(List.of("UPDATE migration_flag")) // matched as substring
40+
.unusedIndexes(Set.of("idx_orders_admin_report"))
3441
.unconditionalMutationStatements(Set.of("DELETE FROM scratch_pad")) // normalized, case-insensitive
42+
.offsetPaginationSqlFragments(List.of("from admin_report"))
43+
.repeatedStatementThreshold(50) // a generous regression tripwire, not a precise count
44+
.repeatedStatementSqlFragments(List.of("from feature_flags"))
3545
.build();
3646
----
3747

@@ -55,6 +65,14 @@ audits.assertAllClean(schema, excludes);
5565
| `PrimaryKeyPresenceAuditAssertion`
5666
| Exact table name.
5767

68+
| `primaryKeyTypeColumns(Set<String>)`
69+
| `PrimaryKeyTypeAuditAssertion`
70+
| Case-insensitive `table.column` string.
71+
72+
| `duplicateForeignKeyConstraints(Set<String>)`
73+
| `DuplicateForeignKeyAuditAssertion`
74+
| Exact FK constraint name. Excluding one constraint of a duplicate pair drops that relationship's group below two members, suppressing the finding entirely.
75+
5876
| `foreignKeyIndexConstraints(Set<String>)`
5977
| `ForeignKeyIndexAuditAssertion`
6078
| Exact FK constraint name.
@@ -70,10 +88,17 @@ audits.assertAllClean(schema, excludes);
7088
| `redundantIndexes(Set<String>)`
7189
| `RedundantIndexAuditAssertion`
7290
| Exact index name.
91+
92+
| `uniqueIndexNotNullIndexes(Set<String>)`
93+
| `UniqueIndexNotNullAuditAssertion`
94+
| Exact index name.
7395
|===
7496

7597
`PrimaryKeyPresenceAuditAssertion` also excludes the Liquibase bookkeeping tables
7698
(`databasechangelog`, `databasechangeloglock`) automatically, without needing an explicit exclusion.
99+
`PrimaryKeyTypeAuditAssertion` and `UnmappedDatabaseObjectAuditAssertion` do not — since
100+
`databasechangeloglock`'s primary key is a narrow `INT` and neither bookkeeping table is entity-mapped,
101+
exclude them explicitly (see the archetype example ITs).
77102

78103
=== JPA exclusions
79104

@@ -83,6 +108,18 @@ audits.assertAllClean(schema, excludes);
83108
| `jpaExcludedRelations(Set<String>)`
84109
| `SchemaEntityValidationAuditAssertion`
85110
| A `table` or `table.column` name — optionally schema-qualified as `schema.table` / `schema.table.column` — matched case-insensitively. Suppresses a known, acceptable entity/schema mismatch.
111+
112+
| `unmappedDatabaseObjectRelations(Set<String>)`
113+
| `UnmappedDatabaseObjectAuditAssertion`
114+
| A `table` or `table.column` name — optionally schema-qualified — matched case-insensitively. Suppresses a known, acceptable unmapped relation (e.g. a migration-tool bookkeeping table).
115+
116+
| `missingVersionEntities(Set<String>)`
117+
| `MissingVersionAttributeAuditAssertion`
118+
| An entity's fully-qualified name, simple name, or physical table name — matched case-insensitively.
119+
120+
| `eagerCollectionRoles(Set<String>)`
121+
| `EagerCollectionFetchAuditAssertion`
122+
| Exact Hibernate collection role (e.g. `com.acme.Order.items`), matched case-insensitively.
86123
|===
87124

88125
=== Runtime exclusions
@@ -98,9 +135,25 @@ audits.assertAllClean(schema, excludes);
98135
| `WhereClauseIndexAuditAssertion`, `OrderByIndexAuditAssertion`, `JoinIndexAuditAssertion`
99136
| Substring match against the full SQL statement.
100137

138+
| `unusedIndexes(Set<String>)`
139+
| `UnusedIndexAuditAssertion`
140+
| Exact index name.
141+
101142
| `unconditionalMutationStatements(Set<String>)`
102143
| `UnconditionalMutationAuditAssertion`
103144
| Case-insensitive match against the normalized statement text.
145+
146+
| `offsetPaginationSqlFragments(List<String>)`
147+
| `OffsetPaginationAuditAssertion`
148+
| Case-insensitive substring match against the normalized statement text.
149+
150+
| `repeatedStatementThreshold(int)`
151+
| `RepeatedStatementAuditAssertion`
152+
| The minimum capture count (inclusive, at least 2) for a `SELECT` shape to be reported; defaults to a generous 50 as a regression tripwire rather than a precise N+1 count.
153+
154+
| `repeatedStatementSqlFragments(List<String>)`
155+
| `RepeatedStatementAuditAssertion`
156+
| Case-insensitive substring match against the normalized statement text.
104157
|===
105158

106159
== Passing exclusions to individual assertion beans

integration/src/site/asciidoc/usage.adoc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,10 @@ audits.assertJpaClean();
7171
=== Runtime audits
7272

7373
Runtime audits intercept every SQL statement executed during the test via Hibernate's `StatementInspector`. The
74-
plan-based audits (`WhereClauseIndexAudit`, `OrderByIndexAudit`, `JoinIndexAudit`) additionally analyze each
75-
statement via `EXPLAIN` and are *PostgreSQL 16+ only*. `UnconditionalMutationAudit` uses a token scan and runs
76-
on every supported platform.
74+
plan-based audits (`WhereClauseIndexAudit`, `OrderByIndexAudit`, `JoinIndexAudit`, `UnusedIndexAudit`)
75+
additionally analyze each statement via `EXPLAIN` and are *PostgreSQL 16+ only*. The token-scan audits
76+
(`UnconditionalMutationAudit`, `OffsetPaginationAudit`, `RepeatedStatementAudit`) scan the captured SQL text
77+
directly and run on every supported platform.
7778

7879
[[postgresql-jdbc-requirement]]
7980
==== PostgreSQL JDBC requirement: `preferQueryMode=simple`

0 commit comments

Comments
 (0)