001: /*
002: *
003: * The DbUnit Database Testing Framework
004: * Copyright (C)2002-2004, DbUnit.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: */
021:
022: package org.dbunit.operation;
023:
024: import org.slf4j.Logger;
025: import org.slf4j.LoggerFactory;
026:
027: import org.dbunit.DatabaseUnitException;
028: import org.dbunit.database.IDatabaseConnection;
029: import org.dbunit.database.statement.IPreparedBatchStatement;
030: import org.dbunit.database.statement.SimplePreparedStatement;
031: import org.dbunit.dataset.Column;
032: import org.dbunit.dataset.DataSetException;
033: import org.dbunit.dataset.IDataSet;
034: import org.dbunit.dataset.ITable;
035: import org.dbunit.dataset.ITableIterator;
036: import org.dbunit.dataset.ITableMetaData;
037: import org.dbunit.dataset.NoPrimaryKeyException;
038: import org.dbunit.dataset.RowOutOfBoundsException;
039: import org.dbunit.dataset.datatype.DataType;
040:
041: import java.sql.PreparedStatement;
042: import java.sql.ResultSet;
043: import java.sql.SQLException;
044: import java.util.BitSet;
045:
046: /**
047: * This operation literally refreshes dataset contents into the database. This
048: * means that data of existing rows is updated and non-existing row get
049: * inserted. Any rows which exist in the database but not in dataset stay
050: * unaffected.
051: *
052: * @author Manuel Laflamme
053: * @version $Revision: 558 $
054: * @since Feb 19, 2002
055: */
056: public class RefreshOperation extends AbstractOperation {
057:
058: /**
059: * Logger for this class
060: */
061: private static final Logger logger = LoggerFactory
062: .getLogger(RefreshOperation.class);
063:
064: private final InsertOperation _insertOperation;
065: private final UpdateOperation _updateOperation;
066:
067: RefreshOperation() {
068: _insertOperation = (InsertOperation) DatabaseOperation.INSERT;
069: _updateOperation = (UpdateOperation) DatabaseOperation.UPDATE;
070: }
071:
072: private boolean isEmpty(ITable table) throws DataSetException {
073: logger.debug("isEmpty(table=" + table + ") - start");
074:
075: return AbstractBatchOperation.isEmpty(table);
076: }
077:
078: ////////////////////////////////////////////////////////////////////////////
079: // DatabaseOperation class
080:
081: public void execute(IDatabaseConnection connection, IDataSet dataSet)
082: throws DatabaseUnitException, SQLException {
083: logger.debug("execute(connection=" + connection
084: + ", dataSet) - start");
085:
086: // for each table
087: ITableIterator iterator = dataSet.iterator();
088: while (iterator.next()) {
089: ITable table = iterator.getTable();
090:
091: // Do not process empty table
092: if (isEmpty(table)) {
093: continue;
094: }
095:
096: ITableMetaData metaData = getOperationMetaData(connection,
097: table.getTableMetaData());
098: RowOperation updateRowOperation = createUpdateOperation(
099: connection, metaData);
100: RowOperation insertRowOperation = new InsertRowOperation(
101: connection, metaData);
102:
103: try {
104: // refresh all rows
105: for (int i = 0;; i++) {
106: if (!updateRowOperation.execute(table, i)) {
107: insertRowOperation.execute(table, i);
108: }
109: }
110: } catch (RowOutOfBoundsException e) {
111: logger.error("execute()", e);
112:
113: // end of table
114: } finally {
115: // cleanup
116: updateRowOperation.close();
117: insertRowOperation.close();
118: }
119: }
120:
121: }
122:
123: private RowOperation createUpdateOperation(
124: IDatabaseConnection connection, ITableMetaData metaData)
125: throws DataSetException, SQLException {
126: logger.debug("createUpdateOperation(connection=" + connection
127: + ", metaData=" + metaData + ") - start");
128:
129: // update only if columns are not all primary keys
130: if (metaData.getColumns().length > metaData.getPrimaryKeys().length) {
131: return new UpdateRowOperation(connection, metaData);
132: }
133:
134: // otherwise, operation only verify if row exist
135: return new RowExistOperation(connection, metaData);
136: }
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
147: .getLogger(RowOperation.class);
148:
149: protected IPreparedBatchStatement _statement;
150: protected OperationData _operationData;
151: protected BitSet _ignoreMapping;
152:
153: /**
154: * Execute this operation on the sepcified table row.
155: * @return <code>true</code> if operation have been executed on the row.
156: */
157: public boolean execute(ITable table, int row)
158: throws DataSetException, SQLException {
159: logger.debug("execute(table=" + table + ", row=" + row
160: + ") - start");
161:
162: Column[] columns = _operationData.getColumns();
163: for (int i = 0; i < columns.length; i++) {
164: // Bind value only if not in ignore mapping
165: if (_ignoreMapping == null || !_ignoreMapping.get(i)) {
166: Object value = table.getValue(row, columns[i]
167: .getColumnName());
168: _statement
169: .addValue(value, columns[i].getDataType());
170: }
171: }
172: _statement.addBatch();
173: int result = _statement.executeBatch();
174: _statement.clearBatch();
175:
176: return result == 1;
177: }
178:
179: /**
180: * Cleanup this operation state.
181: */
182: public void close() throws SQLException {
183: logger.debug("close() - start");
184:
185: if (_statement != null) {
186: _statement.close();
187: }
188: }
189: }
190:
191: /**
192: * Insert row operation.
193: */
194: private class InsertRowOperation extends RowOperation {
195:
196: /**
197: * Logger for this class
198: */
199: private final Logger logger = LoggerFactory
200: .getLogger(InsertRowOperation.class);
201:
202: private IDatabaseConnection _connection;
203: private ITableMetaData _metaData;
204:
205: public InsertRowOperation(IDatabaseConnection connection,
206: ITableMetaData metaData) throws DataSetException,
207: SQLException {
208: _connection = connection;
209: _metaData = metaData;
210: }
211:
212: public boolean execute(ITable table, int row)
213: throws DataSetException, SQLException {
214: logger.debug("execute(table=" + table + ", row=" + row
215: + ") - start");
216:
217: // If current row have a diffrent ignore value mapping than
218: // previous one, we generate a new statement
219: if (_ignoreMapping == null
220: || !_insertOperation.equalsIgnoreMapping(
221: _ignoreMapping, table, row)) {
222: // Execute and close previous statement
223: if (_statement != null) {
224: _statement.close();
225: }
226:
227: _ignoreMapping = _insertOperation.getIgnoreMapping(
228: table, row);
229: _operationData = _insertOperation.getOperationData(
230: _metaData, _ignoreMapping, _connection);
231: _statement = new SimplePreparedStatement(_operationData
232: .getSql(), _connection.getConnection());
233: }
234:
235: return super .execute(table, row);
236: }
237:
238: }
239:
240: /**
241: * Update row operation.
242: */
243: private class UpdateRowOperation extends RowOperation {
244: PreparedStatement _countStatement;
245:
246: public UpdateRowOperation(IDatabaseConnection connection,
247: ITableMetaData metaData) throws DataSetException,
248: SQLException {
249: // setup update statement
250: _operationData = _updateOperation.getOperationData(
251: metaData, null, connection);
252: _statement = new SimplePreparedStatement(_operationData
253: .getSql(), connection.getConnection());
254: }
255: }
256:
257: /**
258: * This operation verify if a row exists in the database.
259: */
260: private class RowExistOperation extends RowOperation {
261:
262: /**
263: * Logger for this class
264: */
265: private final Logger logger = LoggerFactory
266: .getLogger(RowExistOperation.class);
267:
268: PreparedStatement _countStatement;
269:
270: public RowExistOperation(IDatabaseConnection connection,
271: ITableMetaData metaData) throws DataSetException,
272: SQLException {
273: // setup select count statement
274: _operationData = getSelectCountData(metaData, connection);
275: _countStatement = connection.getConnection()
276: .prepareStatement(_operationData.getSql());
277: }
278:
279: private OperationData getSelectCountData(
280: ITableMetaData metaData, IDatabaseConnection connection)
281: throws DataSetException {
282: logger.debug("getSelectCountData(metaData=" + metaData
283: + ", connection=" + connection + ") - start");
284:
285: Column[] primaryKeys = metaData.getPrimaryKeys();
286:
287: // cannot construct where clause if no primary key
288: if (primaryKeys.length == 0) {
289: throw new NoPrimaryKeyException(metaData.getTableName());
290: }
291:
292: // select count
293: StringBuffer sqlBuffer = new StringBuffer(128);
294: sqlBuffer.append("select COUNT(*) from ");
295: sqlBuffer.append(getQualifiedName(connection.getSchema(),
296: metaData.getTableName(), connection));
297:
298: // where
299: sqlBuffer.append(" where ");
300: for (int i = 0; i < primaryKeys.length; i++) {
301: Column column = primaryKeys[i];
302:
303: if (i > 0) {
304: sqlBuffer.append(" and ");
305: }
306: sqlBuffer.append(getQualifiedName(null, column
307: .getColumnName(), connection));
308: sqlBuffer.append(" = ?");
309: }
310:
311: return new OperationData(sqlBuffer.toString(), primaryKeys);
312: }
313:
314: ////////////////////////////////////////////////////////////////////////
315: // RowOperation class
316:
317: /**
318: * Verify if the specified table row exists in the database.
319: * @return <code>true</code> if row exists.
320: */
321: public boolean execute(ITable table, int row)
322: throws DataSetException, SQLException {
323: logger.debug("execute(table=" + table + ", row=" + row
324: + ") - start");
325:
326: Column[] columns = _operationData.getColumns();
327: for (int i = 0; i < columns.length; i++) {
328: Object value = table.getValue(row, columns[i]
329: .getColumnName());
330: DataType dataType = columns[i].getDataType();
331: dataType.setSqlValue(value, i + 1, _countStatement);
332: }
333:
334: ResultSet resultSet = _countStatement.executeQuery();
335: try {
336: resultSet.next();
337: return resultSet.getInt(1) > 0;
338: } finally {
339: resultSet.close();
340: }
341: }
342:
343: public void close() throws SQLException {
344: logger.debug("close() - start");
345:
346: _countStatement.close();
347: }
348: }
349:
350: }
|