RefreshOperation.java

  1. /*
  2.  *
  3.  * The DbUnit Database Testing Framework
  4.  * Copyright (C)2002-2004, DbUnit.org
  5.  *
  6.  * This library is free software; you can redistribute it and/or
  7.  * modify it under the terms of the GNU Lesser General Public
  8.  * License as published by the Free Software Foundation; either
  9.  * version 2.1 of the License, or (at your option) any later version.
  10.  *
  11.  * This library is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14.  * Lesser General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU Lesser General Public
  17.  * License along with this library; if not, write to the Free Software
  18.  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  19.  *
  20.  */

  21. package org.dbunit.operation;

  22. import org.slf4j.Logger;
  23. import org.slf4j.LoggerFactory;

  24. import org.dbunit.DatabaseUnitException;
  25. import org.dbunit.database.IDatabaseConnection;
  26. import org.dbunit.database.statement.IPreparedBatchStatement;
  27. import org.dbunit.database.statement.SimplePreparedStatement;
  28. import org.dbunit.dataset.Column;
  29. import org.dbunit.dataset.DataSetException;
  30. import org.dbunit.dataset.IDataSet;
  31. import org.dbunit.dataset.ITable;
  32. import org.dbunit.dataset.ITableIterator;
  33. import org.dbunit.dataset.ITableMetaData;
  34. import org.dbunit.dataset.NoPrimaryKeyException;
  35. import org.dbunit.dataset.RowOutOfBoundsException;
  36. import org.dbunit.dataset.datatype.DataType;

  37. import java.sql.PreparedStatement;
  38. import java.sql.ResultSet;
  39. import java.sql.SQLException;
  40. import java.util.BitSet;

  41. /**
  42.  * This operation literally refreshes dataset contents into the database. This
  43.  * means that data of existing rows is updated and non-existing row get
  44.  * inserted. Any rows which exist in the database but not in dataset stay
  45.  * unaffected.
  46.  *
  47.  * @author Manuel Laflamme
  48.  * @version $Revision$
  49.  * @since Feb 19, 2002
  50.  */
  51. public class RefreshOperation extends AbstractOperation
  52. {

  53.     /**
  54.      * Logger for this class
  55.      */
  56.     private static final Logger logger = LoggerFactory.getLogger(RefreshOperation.class);

  57.     private final InsertOperation _insertOperation;
  58.     private final UpdateOperation _updateOperation;

  59.     RefreshOperation()
  60.     {
  61.         _insertOperation = (InsertOperation)DatabaseOperation.INSERT;
  62.         _updateOperation = (UpdateOperation)DatabaseOperation.UPDATE;
  63.     }

  64.     private boolean isEmpty(ITable table) throws DataSetException
  65.     {
  66.         return AbstractBatchOperation.isEmpty(table);
  67.     }

  68.     ////////////////////////////////////////////////////////////////////////////
  69.     // DatabaseOperation class

  70.     public void execute(IDatabaseConnection connection, IDataSet dataSet)
  71.             throws DatabaseUnitException, SQLException
  72.     {
  73.         logger.debug("execute(connection={}, dataSet) - start", connection);
  74.        
  75.         // for each table
  76.         ITableIterator iterator = dataSet.iterator();
  77.         while (iterator.next())
  78.         {
  79.             ITable table = iterator.getTable();
  80.            
  81.             String tableName=table.getTableMetaData().getTableName();
  82.             logger.trace("execute: processing table='{}'", tableName);

  83.             // Do not process empty table
  84.             if (isEmpty(table))
  85.             {
  86.                 continue;
  87.             }

  88.             ITableMetaData metaData = getOperationMetaData(connection,
  89.                     table.getTableMetaData());
  90.             RowOperation updateRowOperation = createUpdateOperation(connection,
  91.                     metaData);
  92.             RowOperation insertRowOperation = new InsertRowOperation(connection,
  93.                     metaData);

  94.             try
  95.             {
  96.                 // refresh all rows
  97.                 for (int i = 0; ; i++)
  98.                 {
  99.                     if (!updateRowOperation.execute(table, i))
  100.                     {
  101.                         insertRowOperation.execute(table, i);
  102.                     }
  103.                 }
  104.             }
  105.             catch (RowOutOfBoundsException e)
  106.             {
  107.                 // This exception occurs when records are exhausted
  108.                 // and we reach the end of the table.  Ignore this error.

  109.                 // end of table
  110.             }
  111.             catch (SQLException e)
  112.             {
  113.                 final String msg =
  114.                     "Exception processing table name='" + tableName + "'";
  115.                 throw new DatabaseUnitException(msg, e);
  116.             }
  117.             finally
  118.             {
  119.                 // cleanup
  120.                 updateRowOperation.close();
  121.                 insertRowOperation.close();
  122.             }
  123.         }

  124.     }

  125.     private RowOperation createUpdateOperation(IDatabaseConnection connection,
  126.             ITableMetaData metaData)
  127.             throws DataSetException, SQLException
  128.     {
  129.         logger.debug("createUpdateOperation(connection={}, metaData={}) - start", connection, metaData);

  130.         // update only if columns are not all primary keys
  131.         if (metaData.getColumns().length > metaData.getPrimaryKeys().length)
  132.         {
  133.             return new UpdateRowOperation(connection, metaData);
  134.         }

  135.         // otherwise, operation only verify if row exist
  136.         return new RowExistOperation(connection,  metaData);
  137.     }

  138.     /**
  139.      * This class represents a operation executed on a single table row.
  140.      */
  141.     class RowOperation
  142.     {

  143.         /**
  144.          * Logger for this class
  145.          */
  146.         private final Logger logger = LoggerFactory.getLogger(RowOperation.class);

  147.         protected IPreparedBatchStatement _statement;
  148.         protected OperationData _operationData;
  149.         protected BitSet _ignoreMapping;

  150.         /**
  151.          * Execute this operation on the sepcified table row.
  152.          * @return <code>true</code> if operation have been executed on the row.
  153.          */
  154.         public boolean execute(ITable table, int row)
  155.                 throws DataSetException, SQLException
  156.         {
  157.             logger.debug("execute(table={}, row={}) - start", table, String.valueOf(row));

  158.             Column[] columns = _operationData.getColumns();
  159.             for (int i = 0; i < columns.length; i++)
  160.             {
  161.                 // Bind value only if not in ignore mapping
  162.                 if (_ignoreMapping == null || !_ignoreMapping.get(i))
  163.                 {
  164.                     Object value = table.getValue(row, columns[i].getColumnName());
  165.                     _statement.addValue(value, columns[i].getDataType());
  166.                 }
  167.             }
  168.             _statement.addBatch();
  169.             int result = _statement.executeBatch();
  170.             _statement.clearBatch();

  171.             return result == 1;
  172.         }

  173.         /**
  174.          * Cleanup this operation state.
  175.          */
  176.         public void close() throws SQLException
  177.         {
  178.             logger.debug("close() - start");

  179.             if (_statement != null)
  180.             {
  181.                 _statement.close();
  182.             }
  183.         }
  184.     }

  185.     /**
  186.      * Insert row operation.
  187.      */
  188.     private class InsertRowOperation extends RowOperation
  189.     {

  190.         /**
  191.          * Logger for this class
  192.          */
  193.         private final Logger logger = LoggerFactory.getLogger(InsertRowOperation.class);

  194.         private IDatabaseConnection _connection;
  195.         private ITableMetaData _metaData;

  196.         public InsertRowOperation(IDatabaseConnection connection,
  197.                 ITableMetaData metaData)
  198.                 throws DataSetException, SQLException
  199.         {
  200.             _connection = connection;
  201.             _metaData = metaData;
  202.         }

  203.         public boolean execute(ITable table, int row)
  204.                 throws DataSetException, SQLException
  205.         {
  206.             logger.debug("execute(table={}, row={}) - start", table, row);

  207.             // If current row has a different ignore value mapping than
  208.             // previous one, we generate a new statement
  209.             if (_ignoreMapping == null ||
  210.                     !_insertOperation.equalsIgnoreMapping(_ignoreMapping, table, row))
  211.             {
  212.                 // Execute and close previous statement
  213.                 if (_statement != null)
  214.                 {
  215.                     _statement.close();
  216.                 }

  217.                 _ignoreMapping = _insertOperation.getIgnoreMapping(table, row);
  218.                 _operationData = _insertOperation.getOperationData(_metaData,
  219.                         _ignoreMapping, _connection);
  220.                 _statement = new SimplePreparedStatement(_operationData.getSql(),
  221.                         _connection.getConnection());
  222.             }

  223.             return super.execute(table, row);
  224.         }

  225.     }

  226.     /**
  227.      * Update row operation.
  228.      */
  229.     private class UpdateRowOperation extends RowOperation
  230.     {
  231.         PreparedStatement _countStatement;

  232.         public UpdateRowOperation(IDatabaseConnection connection,
  233.                 ITableMetaData metaData)
  234.                 throws DataSetException, SQLException
  235.         {
  236.             // setup update statement
  237.             _operationData = _updateOperation.getOperationData(
  238.                     metaData, null, connection);
  239.             _statement = new SimplePreparedStatement(_operationData.getSql(),
  240.                     connection.getConnection());
  241.         }
  242.     }

  243.     /**
  244.      * This operation verify if a row exists in the database.
  245.      */
  246.     private class RowExistOperation extends RowOperation
  247.     {

  248.         /**
  249.          * Logger for this class
  250.          */
  251.         private final Logger logger = LoggerFactory.getLogger(RowExistOperation.class);

  252.         PreparedStatement _countStatement;

  253.         public RowExistOperation(IDatabaseConnection connection,
  254.                 ITableMetaData metaData)
  255.                 throws DataSetException, SQLException
  256.         {
  257.             // setup select count statement
  258.             _operationData = getSelectCountData(metaData, connection);
  259.             _countStatement = connection.getConnection().prepareStatement(
  260.                     _operationData.getSql());
  261.         }

  262.         private OperationData getSelectCountData(
  263.                 ITableMetaData metaData, IDatabaseConnection connection) throws DataSetException
  264.         {
  265.             logger.debug("getSelectCountData(metaData={}, connection={}) - start", metaData, connection);

  266.             Column[] primaryKeys = metaData.getPrimaryKeys();

  267.             // cannot construct where clause if no primary key
  268.             if (primaryKeys.length == 0)
  269.             {
  270.                 throw new NoPrimaryKeyException(metaData.getTableName());
  271.             }

  272.             // select count
  273.             final StringBuilder sqlBuffer = new StringBuilder(128);
  274.             sqlBuffer.append("select COUNT(*) from ");
  275.             sqlBuffer.append(getQualifiedName(connection.getSchema(), metaData.getTableName(), connection));

  276.             // where
  277.             sqlBuffer.append(" where ");
  278.             for (int i = 0; i < primaryKeys.length; i++)
  279.             {
  280.                 Column column = primaryKeys[i];

  281.                 if (i > 0)
  282.                 {
  283.                     sqlBuffer.append(" and ");
  284.                 }
  285.                 sqlBuffer.append(getQualifiedName(null, column.getColumnName(), connection));
  286.                 sqlBuffer.append(" = ?");
  287.             }

  288.             return new OperationData(sqlBuffer.toString(), primaryKeys);
  289.         }

  290.         ////////////////////////////////////////////////////////////////////////
  291.         // RowOperation class

  292.         /**
  293.          * Verify if the specified table row exists in the database.
  294.          * @return <code>true</code> if row exists.
  295.          */
  296.         public boolean execute(ITable table, int row)
  297.                 throws DataSetException, SQLException
  298.         {
  299.             logger.debug("execute(table={}, row={}) - start", table, row);

  300.             Column[] columns = _operationData.getColumns();
  301.             for (int i = 0; i < columns.length; i++)
  302.             {
  303.                 Object value = table.getValue(row, columns[i].getColumnName());
  304.                 DataType dataType = columns[i].getDataType();
  305.                 dataType.setSqlValue(value, i + 1, _countStatement);
  306.             }

  307.             ResultSet resultSet = _countStatement.executeQuery();
  308.             try
  309.             {
  310.                 resultSet.next();
  311.                 return resultSet.getInt(1) > 0;
  312.             }
  313.             finally
  314.             {
  315.                 resultSet.close();
  316.             }
  317.         }

  318.         public void close() throws SQLException
  319.         {
  320.             logger.debug("close() - start");

  321.             _countStatement.close();
  322.         }
  323.     }

  324. }