View Javadoc
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  
22  package org.dbunit.operation;
23  
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  
27  import org.dbunit.DatabaseUnitException;
28  import org.dbunit.database.IDatabaseConnection;
29  import org.dbunit.database.statement.IPreparedBatchStatement;
30  import org.dbunit.database.statement.SimplePreparedStatement;
31  import org.dbunit.dataset.Column;
32  import org.dbunit.dataset.DataSetException;
33  import org.dbunit.dataset.IDataSet;
34  import org.dbunit.dataset.ITable;
35  import org.dbunit.dataset.ITableIterator;
36  import org.dbunit.dataset.ITableMetaData;
37  import org.dbunit.dataset.NoPrimaryKeyException;
38  import org.dbunit.dataset.RowOutOfBoundsException;
39  import org.dbunit.dataset.datatype.DataType;
40  
41  import java.sql.PreparedStatement;
42  import java.sql.ResultSet;
43  import java.sql.SQLException;
44  import java.util.BitSet;
45  
46  /**
47   * This operation literally refreshes dataset contents into the database. This
48   * means that data of existing rows is updated and non-existing row get
49   * inserted. Any rows which exist in the database but not in dataset stay
50   * unaffected.
51   *
52   * @author Manuel Laflamme
53   * @version $Revision$
54   * @since Feb 19, 2002
55   */
56  public class RefreshOperation extends AbstractOperation
57  {
58  
59      /**
60       * Logger for this class
61       */
62      private static final Logger logger = LoggerFactory.getLogger(RefreshOperation.class);
63  
64      private final InsertOperation _insertOperation;
65      private final UpdateOperation _updateOperation;
66  
67      RefreshOperation()
68      {
69          _insertOperation = (InsertOperation)DatabaseOperation.INSERT;
70          _updateOperation = (UpdateOperation)DatabaseOperation.UPDATE;
71      }
72  
73      private boolean isEmpty(ITable table) throws DataSetException
74      {
75          return AbstractBatchOperation.isEmpty(table);
76      }
77  
78      ////////////////////////////////////////////////////////////////////////////
79      // DatabaseOperation class
80  
81      public void execute(IDatabaseConnection connection, IDataSet dataSet)
82              throws DatabaseUnitException, SQLException
83      {
84          logger.debug("execute(connection={}, dataSet) - start", connection);
85          
86          // for each table
87          ITableIterator iterator = dataSet.iterator();
88          while (iterator.next())
89          {
90              ITable table = iterator.getTable();
91              
92              String tableName=table.getTableMetaData().getTableName();
93              logger.trace("execute: processing table='{}'", tableName);
94  
95              // Do not process empty table
96              if (isEmpty(table))
97              {
98                  continue;
99              }
100 
101             ITableMetaData metaData = getOperationMetaData(connection,
102                     table.getTableMetaData());
103             RowOperation updateRowOperation = createUpdateOperation(connection,
104                     metaData);
105             RowOperation insertRowOperation = new InsertRowOperation(connection,
106                     metaData);
107 
108             try
109             {
110                 // refresh all rows
111                 for (int i = 0; ; i++)
112                 {
113                     if (!updateRowOperation.execute(table, i))
114                     {
115                         insertRowOperation.execute(table, i);
116                     }
117                 }
118             }
119             catch (RowOutOfBoundsException e)
120             {
121             	// This exception occurs when records are exhausted
122             	// and we reach the end of the table.  Ignore this error.
123 
124                 // end of table
125             }
126             catch (SQLException e)
127             {
128                 final String msg =
129                     "Exception processing table name='" + tableName + "'";
130                 throw new DatabaseUnitException(msg, e);
131             }
132             finally
133             {
134                 // cleanup
135                 updateRowOperation.close();
136                 insertRowOperation.close();
137             }
138         }
139 
140     }
141 
142     private RowOperation createUpdateOperation(IDatabaseConnection connection,
143             ITableMetaData metaData)
144             throws DataSetException, SQLException
145     {
146         logger.debug("createUpdateOperation(connection={}, metaData={}) - start", connection, metaData);
147 
148         // update only if columns are not all primary keys
149         if (metaData.getColumns().length > metaData.getPrimaryKeys().length)
150         {
151             return new UpdateRowOperation(connection, metaData);
152         }
153 
154         // otherwise, operation only verify if row exist
155         return new RowExistOperation(connection,  metaData);
156     }
157 
158     /**
159      * This class represents a operation executed on a single table row.
160      */
161     class RowOperation
162     {
163 
164         /**
165          * Logger for this class
166          */
167         private final Logger logger = LoggerFactory.getLogger(RowOperation.class);
168 
169         protected IPreparedBatchStatement _statement;
170         protected OperationData _operationData;
171         protected BitSet _ignoreMapping;
172 
173         /**
174          * Execute this operation on the sepcified table row.
175          * @return <code>true</code> if operation have been executed on the row.
176          */
177         public boolean execute(ITable table, int row)
178                 throws DataSetException, SQLException
179         {
180             logger.debug("execute(table={}, row={}) - start", table, String.valueOf(row));
181 
182             Column[] columns = _operationData.getColumns();
183             for (int i = 0; i < columns.length; i++)
184             {
185                 // Bind value only if not in ignore mapping
186                 if (_ignoreMapping == null || !_ignoreMapping.get(i))
187                 {
188                     Object value = table.getValue(row, columns[i].getColumnName());
189                     _statement.addValue(value, columns[i].getDataType());
190                 }
191             }
192             _statement.addBatch();
193             int result = _statement.executeBatch();
194             _statement.clearBatch();
195 
196             return result == 1;
197         }
198 
199         /**
200          * Cleanup this operation state.
201          */
202         public void close() throws SQLException
203         {
204             logger.debug("close() - start");
205 
206             if (_statement != null)
207             {
208                 _statement.close();
209             }
210         }
211     }
212 
213     /**
214      * Insert row operation.
215      */
216     private class InsertRowOperation extends RowOperation
217     {
218 
219         /**
220          * Logger for this class
221          */
222         private final Logger logger = LoggerFactory.getLogger(InsertRowOperation.class);
223 
224         private IDatabaseConnection _connection;
225         private ITableMetaData _metaData;
226 
227         public InsertRowOperation(IDatabaseConnection connection,
228                 ITableMetaData metaData)
229                 throws DataSetException, SQLException
230         {
231             _connection = connection;
232             _metaData = metaData;
233         }
234 
235         public boolean execute(ITable table, int row)
236                 throws DataSetException, SQLException
237         {
238             logger.debug("execute(table={}, row={}) - start", table, String.valueOf(row));
239 
240             // If current row has a different ignore value mapping than
241             // previous one, we generate a new statement
242             if (_ignoreMapping == null ||
243                     !_insertOperation.equalsIgnoreMapping(_ignoreMapping, table, row))
244             {
245                 // Execute and close previous statement
246                 if (_statement != null)
247                 {
248                     _statement.close();
249                 }
250 
251                 _ignoreMapping = _insertOperation.getIgnoreMapping(table, row);
252                 _operationData = _insertOperation.getOperationData(_metaData,
253                         _ignoreMapping, _connection);
254                 _statement = new SimplePreparedStatement(_operationData.getSql(),
255                         _connection.getConnection());
256             }
257 
258             return super.execute(table, row);
259         }
260 
261     }
262 
263     /**
264      * Update row operation.
265      */
266     private class UpdateRowOperation extends RowOperation
267     {
268         PreparedStatement _countStatement;
269 
270         public UpdateRowOperation(IDatabaseConnection connection,
271                 ITableMetaData metaData)
272                 throws DataSetException, SQLException
273         {
274             // setup update statement
275             _operationData = _updateOperation.getOperationData(
276                     metaData, null, connection);
277             _statement = new SimplePreparedStatement(_operationData.getSql(),
278                     connection.getConnection());
279         }
280     }
281 
282     /**
283      * This operation verify if a row exists in the database.
284      */
285     private class RowExistOperation extends RowOperation
286     {
287 
288         /**
289          * Logger for this class
290          */
291         private final Logger logger = LoggerFactory.getLogger(RowExistOperation.class);
292 
293         PreparedStatement _countStatement;
294 
295         public RowExistOperation(IDatabaseConnection connection,
296                 ITableMetaData metaData)
297                 throws DataSetException, SQLException
298         {
299             // setup select count statement
300             _operationData = getSelectCountData(metaData, connection);
301             _countStatement = connection.getConnection().prepareStatement(
302                     _operationData.getSql());
303         }
304 
305         private OperationData getSelectCountData(
306                 ITableMetaData metaData, IDatabaseConnection connection) throws DataSetException
307         {
308             logger.debug("getSelectCountData(metaData={}, connection={}) - start", metaData, connection);
309 
310             Column[] primaryKeys = metaData.getPrimaryKeys();
311 
312             // cannot construct where clause if no primary key
313             if (primaryKeys.length == 0)
314             {
315                 throw new NoPrimaryKeyException(metaData.getTableName());
316             }
317 
318             // select count
319             StringBuffer sqlBuffer = new StringBuffer(128);
320             sqlBuffer.append("select COUNT(*) from ");
321             sqlBuffer.append(getQualifiedName(connection.getSchema(), metaData.getTableName(), connection));
322 
323             // where
324             sqlBuffer.append(" where ");
325             for (int i = 0; i < primaryKeys.length; i++)
326             {
327                 Column column = primaryKeys[i];
328 
329                 if (i > 0)
330                 {
331                     sqlBuffer.append(" and ");
332                 }
333                 sqlBuffer.append(getQualifiedName(null, column.getColumnName(), connection));
334                 sqlBuffer.append(" = ?");
335             }
336 
337             return new OperationData(sqlBuffer.toString(), primaryKeys);
338         }
339 
340         ////////////////////////////////////////////////////////////////////////
341         // RowOperation class
342 
343         /**
344          * Verify if the specified table row exists in the database.
345          * @return <code>true</code> if row exists.
346          */
347         public boolean execute(ITable table, int row)
348                 throws DataSetException, SQLException
349         {
350             logger.debug("execute(table={}, row={}) - start", table, String.valueOf(row));
351 
352             Column[] columns = _operationData.getColumns();
353             for (int i = 0; i < columns.length; i++)
354             {
355                 Object value = table.getValue(row, columns[i].getColumnName());
356                 DataType dataType = columns[i].getDataType();
357                 dataType.setSqlValue(value, i + 1, _countStatement);
358             }
359 
360             ResultSet resultSet = _countStatement.executeQuery();
361             try
362             {
363                 resultSet.next();
364                 return resultSet.getInt(1) > 0;
365             }
366             finally
367             {
368                 resultSet.close();
369             }
370         }
371 
372         public void close() throws SQLException
373         {
374             logger.debug("close() - start");
375 
376             _countStatement.close();
377         }
378     }
379 
380 }