Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

replace sequence Ids #30

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion spring-test-dbunit-sample/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
<dependency>
<groupId>com.github.springtestdbunit</groupId>
<artifactId>spring-test-dbunit</artifactId>
<version>1.0.1-SNAPSHOT</version>
<version>1.0.2-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,22 @@
package com.github.springtestdbunit;

import java.lang.annotation.Annotation;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.Column;
import org.dbunit.dataset.DefaultTable;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.ITableIterator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
Expand All @@ -37,8 +44,8 @@
import com.github.springtestdbunit.dataset.DataSetLoader;

/**
* Internal delegate class used to run tests with support for {@link DatabaseSetup &#064;DatabaseSetup},
* {@link DatabaseTearDown &#064;DatabaseTearDown} and {@link ExpectedDatabase &#064;ExpectedDatabase} annotations.
* Internal delegate class used to run tests with support for {@link com.github.springtestdbunit.annotation.DatabaseSetup &#064;DatabaseSetup},
* {@link com.github.springtestdbunit.annotation.DatabaseTearDown &#064;DatabaseTearDown} and {@link com.github.springtestdbunit.annotation.ExpectedDatabase &#064;ExpectedDatabase} annotations.
*
* @author Phillip Webb
* @author Mario Zagar
Expand Down Expand Up @@ -106,7 +113,7 @@ private void verifyExpected(DbUnitTestContext testContext, Collection<ExpectedDa
IDatabaseConnection connection = testContext.getConnection();
IDataSet actualDataSet = connection.createDataSet();
for (ExpectedDatabase annotation : annotations) {
IDataSet expectedDataSet = loadDataset(testContext, annotation.value());
IDataSet expectedDataSet = loadDataset(testContext, annotation.value(), annotation.replaceSequenceIds());
if (expectedDataSet != null) {
if (logger.isDebugEnabled()) {
logger.debug("Veriftying @DatabaseTest expectation using " + annotation.value());
Expand All @@ -117,17 +124,108 @@ private void verifyExpected(DbUnitTestContext testContext, Collection<ExpectedDa
}
}

private IDataSet loadDataset(DbUnitTestContext testContext, String dataSetLocation) throws Exception {
private IDataSet loadDataset(DbUnitTestContext testContext, String dataSetLocation, boolean replaceSequenceIds)
throws Exception {
DataSetLoader dataSetLoader = testContext.getDataSetLoader();
if (StringUtils.hasLength(dataSetLocation)) {
IDataSet dataSet = dataSetLoader.loadDataSet(testContext.getTestClass(), dataSetLocation);
Assert.notNull(dataSet,
"Unable to load dataset from \"" + dataSetLocation + "\" using " + dataSetLoader.getClass());

if (replaceSequenceIds) {
replaceSequences(dataSet, testContext.getConnection());
}

return dataSet;
}
return null;
}

private IDataSet loadDataset(DbUnitTestContext testContext, String dataSetLocation) throws Exception {
return loadDataset(testContext, dataSetLocation, false);
}

/**
* Replaces the sequences names with the sequences values in a DataSet containing the expression "${...}" as a value
* name.
* @see com.github.springtestdbunit.annotation.ExpectedDatabase#replaceSequenceIds()
*
* @param dataSet the dataSet in which we will replace the sequences values.
* @param databaseConnection the connection to the database needed to query the sequences values.
* @throws Exception
*/
private void replaceSequences(IDataSet dataSet, IDatabaseConnection databaseConnection) throws Exception {
ITableIterator iter = dataSet.iterator();

// We iterate on the table names
while (iter.next()) {
ITable table = iter.getTable();
Column[] cols = table.getTableMetaData().getColumns();

// We use this Map the count the number of times a sequence name is used in order to retroactively assign
// an ID based on the sequence value.
Map<String, ArrayList<TableValue>> sequences = new HashMap<String, ArrayList<TableValue>>();

// We iterate on the lines and columns
for (int i = 0; i < table.getRowCount(); i++) {
for (Column column : cols) {

// We verify that the value is not null
Object o = table.getValue(i, column.getColumnName());
if (o != null) {

// We look for the expression "${...}" in the value
String value = o.toString();
if (value.startsWith("${") && value.endsWith("}")) {

// We retrieve the sequence name
String key = value.substring(2, value.length() - 1);

// if the map does not contain the list corresponding to the key we create it
if (sequences.containsKey(key) == false) {
sequences.put(key, new ArrayList<TableValue>());
}

// we stock the data of the element we just found
List<TableValue> liste = sequences.get(key);
TableValue e = new TableValue(i, column.getColumnName());
liste.add(e);
}
}
}
}

// we iterate the sequence names and we replace them in the dataset with the index
// that it should have according to number of times we found it in the dataset
for (String seq : sequences.keySet()) {
List<TableValue> liste = sequences.get(seq);
long nextVal = getNextSequenceValue(seq, databaseConnection);
long idValue = nextVal - liste.size();

for (TableValue e : liste) {
DefaultTable dt = (DefaultTable) table;
dt.setValue(e.getRow(), e.getColumnName(), idValue);
idValue++;
}
}
}
}

/**
* Retrieves the next value in a SQL Sequence.
*
* @param sequenceName
* @param databaseConnection
* @return
* @throws Exception
*/
private long getNextSequenceValue(String sequenceName, IDatabaseConnection databaseConnection) throws Exception {
Statement st = databaseConnection.getConnection().createStatement();
ResultSet rs = st.executeQuery("SELECT " + sequenceName + ".nextval FROM dual");
rs.next();
return rs.getLong(1);
}

private void setupOrTeardown(DbUnitTestContext testContext, boolean isSetup,
Collection<AnnotationAttributes> annotations) throws Exception {
IDatabaseConnection connection = testContext.getConnection();
Expand Down Expand Up @@ -191,4 +289,33 @@ public static <T extends Annotation> Collection<AnnotationAttributes> get(Collec
return annotationAttributes;
}
}

/**
* Utility class to stock the row and columnName of the elements in the DataSet in which there is an Oracle Sequence
* to replace.
*
* @author cachavezley
*/
private class TableValue {
private final int row;
private final String columnName;

private TableValue(int row, String columnName) {
this.row = row;
this.columnName = columnName;
}

private int getRow() {
return this.row;
}

private String getColumnName() {
return this.columnName;
}

@Override
public String toString() {
return "TableValue [row=" + this.row + ", columnName=" + this.columnName + "]";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,27 @@
* @return Database assertion mode to use.
*/
DatabaseAssertionMode assertionMode() default DatabaseAssertionMode.DEFAULT;

/**
* Flag used to indicate that we want to change the sequence names in an XML file for their values in the database.
* <p>
*
* This allows us to use, for example, an xml file for the dataset that looks like this : <br>
* <code>
* <dataset>
* <MYTABLE ID="${SEQ_MYTABLE}" FOO="Hello" />
* <MYTABLE ID="${SEQ_MYTABLE}" FOO="World" />
* <OTHERTABLE ID="${SEQ_OTHERTABLE}" BAR="Hello" />
* <OTHERTABLE ID="${SEQ_OTHERTABLE}" BAR="World" />
* </dataset>
* </code>
*
* The {@link org.dbunit.dataset.IDataSet} however will contain the real ids. Each sequence in the database is only accessed once (to
* know its current value), and the rest of the values are calculated based on that value and the number of times
* the sequence name is declared in the xml file.
*
* @return <code>true</code> if we want to change the sequence names for their values in the database,
* <code>false</code> otherwise
*/
boolean replaceSequenceIds() default false;
}