Skip to content

Limitations

QueryAudit is most useful when a repeatable database test exercises the behavior you want to keep: a bounded number of reads, no unexpected writes, or a query shape you have reviewed. Start with one known query, verify capture, and promote the checks you trust into contracts.

Choose the right audit scope

If your test uses… What to expect Practical approach
MySQL or PostgreSQL features Metadata and plans come from the test database Use the same database engine and schema for those checks. H2 is useful for capture and portable SQL checks
Small or uniform fixtures Plans and repeated-access patterns can differ from larger or skewed data Add representative sizes and associations. A passing test does not establish production latency, throughput, or lock behavior
SELECT bind parameters The built-in EXPLAIN adapters cannot replay captured bind values/types; parameterized SELECTs make enabled EXPLAIN analysis incomplete Use query budgets and supported SQL/metadata checks for this scope. Evaluate plans separately with real parameter values; do not replace placeholders with invented values
Several Spring DataSource beans The extension resolves one source by type Make the audited source unambiguous, normally with @Primary, and ensure the code under test uses it. Wrapping every bean does not mean every source is audited
An existing datasource-proxy The starter can use a query-aware source without adding another wrapper Set query-audit.wrap-data-source.enabled=false only when that proxy is already present; see Spring setup
Dynamic tests from @TestFactory There is no supported per-child audit boundary Use ordinary or parameterized tests for audited cases, or exclude the factory
Concurrent or asynchronous work Each audited method captures into its own session. SQL from another thread counts toward the test while it is the only audit running; during parallel audits it makes the run INCONCLUSIVE Name the pools that tests hand work to in await-executors so the test waits for them. With parallel audits, wrap executor tasks with QueryCaptureSession.wrap; see parallel capture
Loops that repeat a query on purpose A keyset page loop (WHERE id > ? LIMIT ?) binds a new value on each pass and is reported as an N+1. OFFSET pages are not, and repeated lookups with the same values are INFO only Suppress n-plus-one on that test, or page with OFFSET
Virtual threads spring.threads.virtual.enabled runs @Async work on a SimpleAsyncTaskExecutor, which await-executors cannot wait for Define a ThreadPoolTaskExecutor for tests, or wait for the work in the test

The default recommended profile runs no EXPLAIN rule. strict adds the three built-in EXPLAIN rules (full-scan, filesort, and temporary-table). If you enable it but intentionally exclude EXPLAIN, keep these rules disabled in both comparison runs. The resulting contract covers the remaining enabled checks, not execution-plan verification. An unexpected INCONCLUSIVE needs investigation; treating it as PASS removes that distinction.

Query budgets count captured statements, not returned or affected rows. A one-statement query can still read or modify many rows. Keep functional assertions and representative fixtures beside the budget.

Reported cases to check

The following issues were reproduced on 0.6.0 source and remain open in 0.7.2. These links do not claim that 0.7.2 still shows each behavior or that it fixes it. Use each issue's reproduction details when checking your setup.

Workflow Reported behavior Check for your setup
SQL safety and result-size findings Some literal/comment keywords, clause boundaries, and inferred row bounds can cause misses or false positives (#288, #291, #292) Add a known violating control for the SQL shape you gate. Use explicit write budgets where the contract forbids writes, and assert affected/result rows separately
Joined-query EXPLAIN analysis MySQL may omit later plan rows; PostgreSQL may attribute a nested node to the wrong table (#293, #294) Inspect the complete native plan before acting on an index or scan finding
CI comparison Changing only INFO visibility makes a comparison INCONCLUSIVE (#297) Keep report.show-info identical across the runs you compare

Shared annotation policies

Since 0.7.0, composed annotations and audited base classes activate QueryAudit with the same precedence JUnit uses to register it; see composed and inherited annotations. In 0.6.x those declarations registered the extension but left the test unaudited. Before relying on a shared declaration, require one intentional budget failure through it.

Reports and shared CI logs

The default redaction policy applies to machine-readable JSON and GitHub Actions annotations. It does not redact the console or HTML report. Console diagnostics are still printed when JSON is selected. Use synthetic test values and review log/artifact access before sharing them. Extending the policy to human-readable output is tracked in #300; see the current redaction contract.

For reliable CI evidence, select JSON, check its outcome, and verify expected-test coverage. Keep separate output and policy files for separate test JVMs unless your build coordinates their writes. The CI guide describes how to pass settings into the forked test process.