RowCountChecker.java

/*
 *
 * The DbUnit Database Testing Framework
 * Copyright (C)2002-2026, DbUnit.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
package org.dbunit.database.rowcount;

import java.sql.SQLException;

import org.dbunit.DatabaseUnitException;
import org.dbunit.database.DatabaseConfig;
import org.dbunit.database.IDatabaseConnection;

/**
 * Manages a {@link RowCountCheck} baseline across one caller's test lifecycle: lazily
 * resolves a {@link RowCountCheck} on first use - from a connection's
 * {@link org.dbunit.database.DatabaseConfig} by default, layering an enabled flag and
 * excluded table patterns onto it instead when {@link #setEnabledOverride(boolean, String[])}
 * was called, or bypassing resolution entirely when {@link #setRowCountCheck(RowCountCheck)}
 * was - captures a baseline, verifies it later, and lets the baseline be discarded when a
 * verify would be noise - e.g. the caller's own test steps already failed, so the
 * database is in an unknown state and a count difference is not a finding worth its own
 * report.
 * <p>
 * Holds no connection of its own; every method takes the connection to use, leaving
 * acquisition and closing entirely to the caller.
 * <p>
 * Safe to reuse across more than one test's lifecycle - e.g. one instance held by a
 * {@code DefaultPrepAndExpectedTestCase} shared through a {@code @DbUnitTestCase} static
 * field: each {@link #capture(IDatabaseConnection)} call re-resolves the
 * {@link RowCountCheck} from the current {@link #setEnabledOverride(boolean, String[])}/
 * connection-config state rather than reusing whatever an earlier test resolved, unless
 * {@link #setRowCountCheck(RowCountCheck)} pinned one in place explicitly - that one is
 * used as-is for every later test too, until changed. A caller sharing one instance across
 * tests must call {@link #clearEnabledOverride()} for a test that declares no override of
 * its own, so an earlier test's override does not silently carry over.
 *
 * @author Jeff Jensen
 * @since 3.6.0
 */
public class RowCountChecker
{
    private RowCountCheck rowCountCheck;
    private boolean rowCountCheckSetExplicitly;
    private RowCountSnapshot baseline;
    private Boolean enabledOverride;
    private String[] excludeOverride;

    /**
     * Captures the baseline using the given connection, resolving a {@link RowCountCheck}
     * from its {@link org.dbunit.database.DatabaseConfig} first if none has been resolved
     * or set yet. Unless {@link #setRowCountCheck(RowCountCheck)} pinned one in place
     * explicitly, any previously resolved {@link RowCountCheck} is discarded first, so this
     * capture re-resolves from the current {@link #setEnabledOverride(boolean, String[])}/
     * connection-config state instead of reusing a stale resolution left over from an
     * earlier test that reused this same instance.
     *
     * @param connection the connection to capture the baseline from.
     * @throws DatabaseUnitException if enumerating or filtering the tables fails.
     * @throws SQLException if counting a table's rows fails.
     */
    public void capture(final IDatabaseConnection connection)
            throws DatabaseUnitException, SQLException
    {
        if (!rowCountCheckSetExplicitly)
        {
            rowCountCheck = null;
        }
        baseline = resolve(connection).capture(connection);
    }

    /**
     * Verifies the captured baseline against the given connection's current row counts.
     * A no-op that never queries the connection when no baseline was captured - the check
     * is disabled, {@link #capture(IDatabaseConnection)} was never called, or
     * {@link #discardBaseline()} was.
     *
     * @param connection the connection to read the current row counts from.
     * @throws DatabaseUnitException if enumerating or filtering the tables fails, or if any
     *             table's row count no longer matches the baseline
     *             ({@link UnexpectedRowCountException}).
     * @throws SQLException if counting a table's rows fails.
     */
    public void verify(final IDatabaseConnection connection)
            throws DatabaseUnitException, SQLException
    {
        if (baseline == null)
        {
            return;
        }
        resolve(connection).verify(baseline, connection);
    }

    /**
     * Discards the captured baseline, so a later {@link #verify(IDatabaseConnection)} call
     * skips silently instead of comparing against it.
     */
    public void discardBaseline()
    {
        baseline = null;
    }

    /**
     * Returns whether a baseline is currently held, so a caller can tell there is nothing to
     * verify - e.g. to skip acquiring a connection for {@link #verify(IDatabaseConnection)}
     * entirely - without needing one just to ask.
     *
     * @return {@code true} when a baseline was captured and neither consumed by
     *         {@link #discardBaseline()} nor left uncaptured because the check was disabled.
     */
    public boolean hasBaseline()
    {
        return baseline != null;
    }

    private RowCountCheck resolve(final IDatabaseConnection connection)
    {
        if (rowCountCheck == null)
        {
            rowCountCheck =
                    new RowCountCheck(resolveConfiguration(connection.getConfig()));
        }
        return rowCountCheck;
    }

    /**
     * Builds the {@link RowCountCheckConfiguration} to resolve a {@link RowCountCheck} from:
     * straight from {@code connectionConfig} when no override is set, or from
     * {@link #enabledOverride}/{@link #excludeOverride} plus {@code connectionConfig}'s own
     * {@link RowCounter} when one is - so an override controls only the enabled flag and the
     * exclude patterns it declares, and {@code -Ddbunit.rowCountCheck} still wins outright over
     * either, exactly as it would resolving directly from {@code connectionConfig}.
     *
     * @param connectionConfig The connection's own {@link DatabaseConfig}.
     * @return The configuration to resolve a {@link RowCountCheck} from.
     */
    private RowCountCheckConfiguration resolveConfiguration(final DatabaseConfig connectionConfig)
    {
        if (enabledOverride == null)
        {
            return new RowCountCheckConfiguration(connectionConfig);
        }
        return new RowCountCheckConfiguration(enabledOverride, excludeOverride,
                (RowCounter) connectionConfig.getProperty(DatabaseConfig.PROPERTY_ROW_COUNTER));
    }

    /**
     * Returns the {@link RowCountCheck} in use.
     *
     * @return the row count check, or {@code null} if none has been resolved or set yet.
     */
    public RowCountCheck getRowCountCheck()
    {
        return rowCountCheck;
    }

    /**
     * Sets the {@link RowCountCheck} to use, overriding the one otherwise lazily built from
     * a connection's DatabaseConfig on first use.
     *
     * @param rowCountCheck the row count check to use.
     */
    public void setRowCountCheck(final RowCountCheck rowCountCheck)
    {
        this.rowCountCheck = rowCountCheck;
        this.rowCountCheckSetExplicitly = true;
    }

    /**
     * Sets the enabled flag and excluded table patterns to resolve a {@link RowCountCheck}
     * from, instead of a connection's own {@link org.dbunit.database.DatabaseConfig} - the
     * values an annotation such as {@code @DbUnitRowCountCheck} declares, carried over onto
     * whatever connection this checker ends up resolving one from, rather than a caller having
     * to resolve a connection of its own just to read its {@link RowCounter}.
     *
     * @param enabled Whether the check is enabled.
     * @param exclude The excluded table patterns; {@code null} is treated as empty (excludes
     *            none).
     * @since 3.6.0
     */
    public void setEnabledOverride(final boolean enabled, final String[] exclude)
    {
        this.enabledOverride = enabled;
        this.excludeOverride = exclude == null ? new String[0] : exclude.clone();
    }

    /**
     * Clears the enabled flag and excluded table patterns {@link #setEnabledOverride(boolean, String[])}
     * set, returning to resolving a {@link RowCountCheck} from a connection's own
     * {@link org.dbunit.database.DatabaseConfig}.
     *
     * <p>A caller sharing one {@link RowCountChecker} instance across several tests - e.g. a
     * {@code DefaultPrepAndExpectedTestCase} held by a {@code @DbUnitTestCase} static field -
     * must call this for a test that declares no override of its own, so an earlier test's
     * override does not silently carry over onto this one.
     *
     * @since 3.6.0
     */
    public void clearEnabledOverride()
    {
        this.enabledOverride = null;
        this.excludeOverride = null;
    }
}