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;
}
}