DbUnitAssert.java

/*
 *
 * The DbUnit Database Testing Framework
 * Copyright (C)2002-2008, 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.assertion;

import java.sql.SQLException;

import org.dbunit.DatabaseUnitException;
import org.dbunit.assertion.comparer.value.ValueComparers;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.Column;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.datatype.DataType;
import org.dbunit.dataset.datatype.UnknownDataType;
import org.dbunit.dataset.filter.DefaultColumnFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Default implementation of DbUnit assertions, based on the original methods
 * present at {@link org.dbunit.Assertion}.
 *
 * All are equality comparisons.
 *
 * @author Felipe Leme (dbunit@felipeal.net)
 * @author gommma (gommma AT users.sourceforge.net)
 * @version $Revision$ $Date$
 * @since 2.4.0
 */
public class DbUnitAssert extends DbUnitAssertBase
{
    private static final Logger logger =
            LoggerFactory.getLogger(DbUnitAssert.class);

    /**
     * Compare one table present in two datasets ignoring specified columns.
     *
     * @param expectedDataset
     *            First dataset.
     * @param actualDataset
     *            Second dataset.
     * @param tableName
     *            Table name of the table to be compared.
     * @param ignoreCols
     *            Columns to be ignored in comparison.
     * @throws org.dbunit.DatabaseUnitException
     *             If an error occurs.
     */
    public void assertEqualsIgnoreCols(final IDataSet expectedDataset,
            final IDataSet actualDataset, final String tableName,
            final String[] ignoreCols) throws DatabaseUnitException
    {
        logger.debug(
                "assertEqualsIgnoreCols(expectedDataset={}, actualDataset={}, tableName={}, ignoreCols={}) - start",
                expectedDataset, actualDataset, tableName, ignoreCols);

        assertEqualsIgnoreCols(expectedDataset.getTable(tableName),
                actualDataset.getTable(tableName), ignoreCols);
    }

    /**
     * Compare the given tables ignoring specified columns.
     *
     * @param expectedTable
     *            First table.
     * @param actualTable
     *            Second table.
     * @param ignoreCols
     *            Columns to be ignored in comparison.
     * @throws org.dbunit.DatabaseUnitException
     *             If an error occurs.
     */
    public void assertEqualsIgnoreCols(final ITable expectedTable,
            final ITable actualTable, final String[] ignoreCols)
            throws DatabaseUnitException
    {
        logger.debug(
                "assertEqualsIgnoreCols(expectedTable={}, actualTable={}, ignoreCols={}) - start",
                expectedTable, actualTable, ignoreCols);

        final ITable expectedTableFiltered = DefaultColumnFilter
                .excludedColumnsTable(expectedTable, ignoreCols);
        final ITable actualTableFiltered = DefaultColumnFilter
                .excludedColumnsTable(actualTable, ignoreCols);
        assertEquals(expectedTableFiltered, actualTableFiltered);
    }

    /**
     * Compare a table from a dataset with a table generated from an sql query.
     *
     * @param expectedDataset
     *            Dataset to retrieve the first table from.
     * @param connection
     *            Connection to use for the SQL statement.
     * @param sqlQuery
     *            SQL query that will build the data in returned second table
     *            rows.
     * @param tableName
     *            Table name of the table to compare.
     * @param ignoreCols
     *            Columns to be ignored in comparison.
     * @throws DatabaseUnitException
     *             If an error occurs while performing the comparison.
     * @throws java.sql.SQLException
     *             If an SQL error occurs.
     */
    public void assertEqualsByQuery(final IDataSet expectedDataset,
            final IDatabaseConnection connection, final String sqlQuery,
            final String tableName, final String[] ignoreCols)
            throws DatabaseUnitException, SQLException
    {
        logger.debug(
                "assertEqualsByQuery(expectedDataset={}, connection={}, tableName={}, sqlQuery={}, ignoreCols={}) - start",
                expectedDataset, connection, tableName, sqlQuery, ignoreCols);

        final ITable expectedTable = expectedDataset.getTable(tableName);
        assertEqualsByQuery(expectedTable, connection, tableName, sqlQuery,
                ignoreCols);
    }

    /**
     * Compare a table with a table generated from an sql query.
     *
     * @param expectedTable
     *            Table containing all expected results.
     * @param connection
     *            Connection to use for the SQL statement.
     * @param tableName
     *            The name of the table to query from the database.
     * @param sqlQuery
     *            SQL query that will build the data in returned second table
     *            rows.
     * @param ignoreCols
     *            Columns to be ignored in comparison.
     * @throws DatabaseUnitException
     *             If an error occurs while performing the comparison.
     * @throws java.sql.SQLException
     *             If an SQL error occurs.
     */
    public void assertEqualsByQuery(final ITable expectedTable,
            final IDatabaseConnection connection, final String tableName,
            final String sqlQuery, final String[] ignoreCols)
            throws DatabaseUnitException, SQLException
    {
        logger.debug(
                "assertEqualsByQuery(expectedTable={}, connection={}, tableName={}, sqlQuery={}, ignoreCols={}) - start",
                expectedTable, connection, tableName, sqlQuery, ignoreCols);

        final ITable expected = DefaultColumnFilter
                .excludedColumnsTable(expectedTable, ignoreCols);
        final ITable queriedTable =
                connection.createQueryTable(tableName, sqlQuery);
        final ITable actual = DefaultColumnFilter
                .excludedColumnsTable(queriedTable, ignoreCols);
        assertEquals(expected, actual);
    }

    /**
     * Asserts that the two specified dataset are equals. This method ignore the
     * tables order.
     */
    public void assertEquals(final IDataSet expectedDataSet,
            final IDataSet actualDataSet) throws DatabaseUnitException
    {
        logger.debug(
                "assertEquals(expectedDataSet={}, actualDataSet={}) - start",
                expectedDataSet, actualDataSet);
        assertEquals(expectedDataSet, actualDataSet, null);
    }

    /**
     * Asserts that the two specified dataset are equals. This method ignore the
     * tables order.
     *
     * @since 2.4
     */
    public void assertEquals(final IDataSet expectedDataSet,
            final IDataSet actualDataSet, final FailureHandler failureHandler)
            throws DatabaseUnitException
    {
        assertWithValueComparer(expectedDataSet, actualDataSet, failureHandler,
                null, null);
    }

    protected void compareTables(final IDataSet expectedDataSet,
            final IDataSet actualDataSet, final String[] expectedNames,
            final FailureHandler failureHandler) throws DatabaseUnitException
    {
        compareTables(expectedDataSet, actualDataSet, expectedNames,
                failureHandler, null, null);
    }

    /**
     * Asserts that the two specified tables are equals. This method ignores the
     * table names, the columns order, the columns data type and which columns
     * are composing the primary keys.
     *
     * @param expectedTable
     *            Table containing all expected results.
     * @param actualTable
     *            Table containing all actual results.
     * @throws DatabaseUnitException
     */
    public void assertEquals(final ITable expectedTable,
            final ITable actualTable) throws DatabaseUnitException
    {
        logger.debug("assertEquals(expectedTable={}, actualTable={}) - start",
                expectedTable, actualTable);
        assertEquals(expectedTable, actualTable, (Column[]) null);
    }

    /**
     * Asserts that the two specified tables are equals. This method ignores the
     * table names, the columns order, the columns data type and which columns
     * are composing the primary keys. <br />
     * Example: <code><pre>
     * ITable actualTable = ...;
     * ITable expectedTable = ...;
     * ITableMetaData metaData = actualTable.getTableMetaData();
     * Column[] additionalInfoCols = Columns.getColumns(new String[] {"MY_PK_COLUMN"}, metaData.getColumns());
     * assertEquals(expectedTable, actualTable, additionalInfoCols);
     * </pre></code>
     *
     * @param expectedTable
     *            Table containing all expected results.
     * @param actualTable
     *            Table containing all actual results.
     * @param additionalColumnInfo
     *            The columns to be printed out if the assert fails because of a
     *            data mismatch. Provides some additional column values that may
     *            be useful to quickly identify the columns for which the
     *            mismatch occurred (for example a primary key column). Can be
     *            <code>null</code>.
     * @throws DatabaseUnitException
     */
    public void assertEquals(final ITable expectedTable,
            final ITable actualTable, final Column[] additionalColumnInfo)
            throws DatabaseUnitException
    {
        logger.debug(
                "assertEquals(expectedTable={}, actualTable={}, additionalColumnInfo={}) - start",
                expectedTable, actualTable, additionalColumnInfo);

        FailureHandler failureHandler = null;
        if (additionalColumnInfo != null)
        {
            failureHandler = getDefaultFailureHandler(additionalColumnInfo);
        }

        assertEquals(expectedTable, actualTable, failureHandler);
    }

    /**
     * Asserts that the two specified tables are equals. This method ignores the
     * table names, the columns order, the columns data type and which columns
     * are composing the primary keys. <br />
     * Example: <code><pre>
     * ITable actualTable = ...;
     * ITable expectedTable = ...;
     * ITableMetaData metaData = actualTable.getTableMetaData();
     * FailureHandler failureHandler = new DefaultFailureHandler();
     * assertEquals(expectedTable, actualTable, failureHandler);
     * </pre></code>
     *
     * @param expectedTable
     *            Table containing all expected results.
     * @param actualTable
     *            Table containing all actual results.
     * @param failureHandler
     *            The failure handler used if the assert fails because of a data
     *            mismatch. Provides some additional information that may be
     *            useful to quickly identify the rows for which the mismatch
     *            occurred (for example by printing an additional primary key
     *            column). Can be <code>null</code>.
     * @throws DatabaseUnitException
     * @since 2.4
     */
    public void assertEquals(final ITable expectedTable,
            final ITable actualTable, final FailureHandler failureHandler)
            throws DatabaseUnitException
    {
        assertWithValueComparer(expectedTable, actualTable, failureHandler,
                ValueComparers.isActualEqualToExpectedWithEmptyFailMessage,
                null);
    }

    /**
     * Represents a single column to be used for the comparison of table data.
     * It contains the {@link DataType} to be used for comparing the given
     * column. This {@link DataType} matches the expected and actual column's
     * datatype.
     *
     * @author gommma (gommma AT users.sourceforge.net)
     * @author Last changed by: $Author: gommma $
     * @version $Revision: 864 $ $Date: 2008-11-07 06:27:26 -0800 (Fri, 07 Nov
     *          2008) $
     * @since 2.4.0
     */
    public static class ComparisonColumn
    {
        private static final Logger logger =
                LoggerFactory.getLogger(ComparisonColumn.class);

        private String columnName;
        private DataType dataType;

        /**
         * @param tableName
         *            The table name which is only needed for debugging output.
         * @param expectedColumn
         *            The expected column needed to resolve the {@link DataType}
         *            to use for the actual comparison.
         * @param actualColumn
         *            The actual column needed to resolve the {@link DataType}
         *            to use for the actual comparison.
         * @param failureHandler
         *            The {@link FailureHandler} to be used when no datatype can
         *            be determined.
         */
        public ComparisonColumn(final String tableName,
                final Column expectedColumn, final Column actualColumn,
                final FailureHandler failureHandler)
        {
            this.columnName = expectedColumn.getColumnName();
            this.dataType = getComparisonDataType(tableName, expectedColumn,
                    actualColumn, failureHandler);
        }

        /**
         * @return The column actually being compared.
         */
        public String getColumnName()
        {
            return this.columnName;
        }

        /**
         * @return The {@link DataType} to use for the actual comparison.
         */
        public DataType getDataType()
        {
            return this.dataType;
        }

        /**
         * @param tableName
         *            The table name which is only needed for debugging output.
         * @param expectedColumn
         * @param actualColumn
         * @param failureHandler
         *            The {@link FailureHandler} to be used when no datatype can
         *            be determined.
         * @return The dbunit {@link DataType} to use for comparing the given
         *         column.
         */
        private DataType getComparisonDataType(final String tableName,
                final Column expectedColumn, final Column actualColumn,
                final FailureHandler failureHandler)
        {
            logger.debug(
                    "getComparisonDataType(tableName={}, expectedColumn={}, actualColumn={}, failureHandler={}) - start",
                    tableName, expectedColumn, actualColumn, failureHandler);

            final DataType expectedDataType = expectedColumn.getDataType();
            final DataType actualDataType = actualColumn.getDataType();

            // The two columns have different data type
            if (!expectedDataType.getClass().isInstance(actualDataType))
            {
                // Expected column data type is unknown, use actual column data
                // type
                if (expectedDataType instanceof UnknownDataType)
                {
                    return actualDataType;
                }

                // Actual column data type is unknown, use expected column data
                // type
                if (actualDataType instanceof UnknownDataType)
                {
                    return expectedDataType;
                }

                // Impossible to determine which data type to use
                final String msg = "Incompatible data types: (table="
                        + tableName + ", col=" + expectedColumn.getColumnName()
                        + ")";
                throw failureHandler.createFailure(msg,
                        String.valueOf(expectedDataType),
                        String.valueOf(actualDataType));
            }

            // Both columns have same data type, return any one of them
            return expectedDataType;
        }
    }
}