RepeatedStatementAuditAssertion.java

package io.github.databaseaudits.spring.boot.assertion;

import java.util.List;

import io.github.databaseaudits.audit.runtime.RepeatedStatementAudit;
import io.github.databaseaudits.platform.DatabasePlatform;

/**
 * Asserts that no captured {@code SELECT} shape ran at least the threshold
 * number of times (or is excluded) — the signature of an N+1 statement burst
 * — using {@link RepeatedStatementAudit}.
 */
public class RepeatedStatementAuditAssertion extends AbstractAuditAssertion {
    private static final String MESSAGE =
            "Repeated SELECT statement shapes (N+1 burst) — eliminate with a fetch join, @EntityGraph, or "
                    + "@BatchSize/hibernate.default_batch_fetch_size, or exclude the statement if the repetition "
                    + "is deliberate.";

    private final RepeatedStatementAudit audit;

    /**
     * Constructs the assertion around its audit.
     *
     * @param audit
     *                  the underlying audit.
     * @param platform
     *                  the database platform the audit runs against, stamped on
     *                  any raised failure for the fix renderer.
     */
    public RepeatedStatementAuditAssertion(final RepeatedStatementAudit audit,
            final DatabasePlatform platform) {
        super(platform);
        this.audit = audit;
    }

    /**
     * Asserts that no captured {@code SELECT} shape ran at least
     * {@code threshold} times.
     *
     * @param threshold
     *                      the minimum capture count (inclusive) for a
     *                      statement shape to be reported; must be at least 2.
     */
    public void assertClean(final int threshold) {
        assertClean(threshold, List.of());
    }

    /**
     * Asserts that no captured {@code SELECT} shape ran at least
     * {@code threshold} times, ignoring statements matching the excluded
     * fragments.
     *
     * @param threshold
     *                                 the minimum capture count (inclusive)
     *                                 for a statement shape to be reported;
     *                                 must be at least 2.
     * @param excludedSqlFragments
     *                                 the SQL fragments whose statements to
     *                                 exclude.
     */
    public void assertClean(final int threshold,
            final List<String> excludedSqlFragments) {
        failOnViolations(MESSAGE,
                audit.audit(threshold, excludedSqlFragments));
    }

    @Override
    public AuditFamily family() {
        return AuditFamily.RUNTIME;
    }

    @Override
    public void assertClean(final AuditScope scope) {
        assertClean(scope.excludes().repeatedStatementThreshold(),
                scope.excludes().repeatedStatementSqlFragments());
    }
}