/**
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* Copyright 2012-2016 the original author or authors.
*/
package org.assertj.db.type;
import org.assertj.db.exception.AssertJDBException;
import org.assertj.db.util.ChangeComparator;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import static org.assertj.db.type.Change.*;
/**
* Changes in the database.
*
* @author RĂ©gis Pouiller
*
*/
public class Changes extends AbstractDbElement<Changes> {
/**
* The list of the tables.
*/
private List<Table> tablesList;
/**
* The list of the tables at start point.
*/
private List<Table> tablesAtStartPointList;
/**
* The list of the tables at end point.
*/
private List<Table> tablesAtEndPointList;
/**
* The request.
*/
private Request request;
/**
* The request at start point.
*/
private Request requestAtStartPoint;
/**
* The request at end point.
*/
private Request requestAtEndPoint;
/**
* The list of the changes.
*/
private List<Change> changesList;
/**
* Constructor.
*/
public Changes() {
super(Changes.class);
}
/**
* Constructor.
*
* @param source The {@link Source} to connect to the database (must be not {@code null}).
* @throws NullPointerException If {@code source} is {@code null}.
*/
public Changes(Source source) {
super(Changes.class, source);
}
/**
* Constructor.
*
* @param dataSource The {@link DataSource} (must be not {@code null}).
* @throws NullPointerException If {@code dataSource} is {@code null}.
*/
public Changes(DataSource dataSource) {
super(Changes.class, dataSource);
}
/**
* Constructor.
*
* @param tables Table on which are the comparison.
*/
public Changes(Table... tables) {
super(Changes.class);
setTables(tables);
}
/**
* Constructor.
*
* @param request Request on which are the comparison.
*/
public Changes(Request request) {
super(Changes.class);
setRequest(request);
}
/**
* Sets the table on which are the comparison.
*
* @param tables Table on which are the comparison.
* @return {@code this} actual instance.
*/
public Changes setTables(Table... tables) {
request = null;
requestAtStartPoint = null;
requestAtEndPoint = null;
tablesList = new ArrayList<>();
tablesAtStartPointList = null;
tablesAtEndPointList = null;
changesList = null;
for (Table table : tables) {
if (table == null) {
throw new NullPointerException("The tables must be not null");
}
Table t = getDuplicatedTable(table);
tablesList.add(t);
}
if (tables.length > 0) {
copyElement(tables[0], this);
}
return myself;
}
/**
* Returns the list of {@link Table}.
* @return The list of {@link Table}.
*/
public List<Table> getTablesList() {
return tablesList;
}
/**
* Sets the {@link Request}.
*
* @param request The {@link Request}.
* @return {@code this} actual instance.
*/
public Changes setRequest(Request request) {
if (request == null) {
throw new NullPointerException("The request must be not null");
}
tablesList = null;
tablesAtStartPointList = null;
tablesAtEndPointList = null;
this.request = getDuplicatedRequest(request);
copyElement(request, this);
requestAtStartPoint = null;
requestAtEndPoint = null;
changesList = null;
return myself;
}
/**
* Returns the {@link Request}.
* @return The {@link Request}.
*/
public Request getRequest() {
return request;
}
/**
* Returns the list of the {@link Table}s at start point.
*
* @return The list of the {@link Table}s at start point.
* @see Changes#setStartPointNow()
*/
public List<Table> getTablesAtStartPointList() {
return tablesAtStartPointList;
}
/**
* Returns the list of the {@link Table}s at end point.
*
* @return The list of the {@link Table}s at end point.
* @see Changes#setEndPointNow()
*/
public List<Table> getTablesAtEndPointList() {
return tablesAtEndPointList;
}
/**
* Returns the {@link Request} at start point.
*
* @return The {@link Request} at start point.
* @see Changes#setStartPointNow()
*/
public Request getRequestAtStartPoint() {
return requestAtStartPoint;
}
/**
* Returns the {@link Request} at end point.
*
* @return The {@link Request} at end point.
* @see Changes#setEndPointNow()
*/
public Request getRequestAtEndPoint() {
return requestAtEndPoint;
}
/**
* Copy a {@link AbstractDbElement} in parameter on another.
*
* @param elementToCopy The {@link AbstractDbElement} to copy
* @param element The {@link AbstractDbElement} on which is the copy
*/
private static void copyElement(AbstractDbElement<?> elementToCopy, AbstractDbElement<?> element) {
if (elementToCopy.getSource() != null) {
element.setSource(elementToCopy.getSource());
}
if (elementToCopy.getDataSource() != null) {
element.setDataSource(elementToCopy.getDataSource());
}
}
/**
* Duplicate the {@link Request} in parameter and returns it.
*
* @param request The {@link Request} to duplicate
* @return The Duplication
*/
private static Request getDuplicatedRequest(Request request) {
Request r = new Request();
copyElement(request, r);
return r.setLetterCases(request.getTableLetterCase(),
request.getColumnLetterCase(),
request.getPrimaryKeyLetterCase())
.setRequest(request.getRequest())
.setParameters(request.getParameters())
.setPksName(request.getPksNameList().toArray(new String[request.getPksNameList().size()]));
}
/**
* Duplicate the {@link Table} in parameter and returns it.
*
* @param table The {@link Table} to duplicate
* @return The Duplication
*/
private static Table getDuplicatedTable(Table table) {
Table t = new Table();
copyElement(table, t);
return t.setLetterCases(table.getTableLetterCase(),
table.getColumnLetterCase(),
table.getPrimaryKeyLetterCase())
.setName(table.getName())
.setColumnsToCheck(table.getColumnsToCheck())
.setColumnsToExclude(table.getColumnsToExclude());
}
/**
* Sets the start point for comparison.
*
* @return {@code this} actual instance.
*/
public Changes setStartPointNow() {
if (request == null && tablesList == null) {
try (Connection connection = getConnection()) {
tablesList = new LinkedList<>();
DatabaseMetaData metaData = connection.getMetaData();
ResultSet resultSet = metaData.getTables(getCatalog(connection), getSchema(connection), null,
new String[] { "TABLE" });
while (resultSet.next()) {
String tableName = resultSet.getString("TABLE_NAME");
Table t = new Table().setLetterCases(getTableLetterCase(), getColumnLetterCase(), getPrimaryKeyLetterCase())
.setName(getTableLetterCase().convert(tableName));
copyElement(this, t);
tablesList.add(t);
}
} catch (SQLException e) {
throw new AssertJDBException(e);
}
}
if (request != null) {
tablesAtStartPointList = null;
requestAtStartPoint = getDuplicatedRequest(request);
requestAtStartPoint.getRowsList();
} else {
requestAtStartPoint = null;
tablesAtStartPointList = new LinkedList<>();
for (Table table : tablesList) {
Table t = getDuplicatedTable(table);
t.getRowsList();
tablesAtStartPointList.add(t);
}
}
tablesAtEndPointList = null;
requestAtEndPoint = null;
changesList = null;
return myself;
}
/**
* Sets the end point for comparison.
*
* @return {@code this} actual instance.
* @throws AssertJDBException If the start point is not set
*/
public Changes setEndPointNow() {
if (requestAtStartPoint == null && tablesAtStartPointList == null) {
throw new AssertJDBException("Start point must be set before");
}
if (requestAtStartPoint != null) {
requestAtEndPoint = getDuplicatedRequest(request);
requestAtEndPoint.getRowsList();
} else {
tablesAtEndPointList = new LinkedList<>();
for (Table table : tablesList) {
Table t = getDuplicatedTable(table);
t.getRowsList();
tablesAtEndPointList.add(t);
}
}
changesList = null;
return myself;
}
/**
* Returns the list of changes for the data when there have primary keys.
*
* @param dataName The name of the data.
* @param dataAtStartPoint The data at start point.
* @param dataAtEndPoint The data at end point.
* @return The list of changes for the data.
*/
private List<Change> getChangesListWithPks(String dataName, AbstractDbData<?> dataAtStartPoint,
AbstractDbData<?> dataAtEndPoint) {
List<Change> changesList = new ArrayList<>();
// List the created rows : the row is not present at the start point
for (Row row : dataAtEndPoint.getRowsList()) {
Row rowAtStartPoint = dataAtStartPoint.getRowFromPksValues(row.getPksValues());
if (rowAtStartPoint == null) {
Change change = createCreationChange(dataAtEndPoint.getDataType(), dataName, row,
getTableLetterCase(), getColumnLetterCase(), getPrimaryKeyLetterCase());
changesList.add(change);
}
}
for (Row row : dataAtStartPoint.getRowsList()) {
Row rowAtEndPoint = dataAtEndPoint.getRowFromPksValues(row.getPksValues());
if (rowAtEndPoint == null) {
// List the deleted rows : the row is not present at the end point
Change change = createDeletionChange(dataAtStartPoint.getDataType(), dataName, row,
getTableLetterCase(), getColumnLetterCase(), getPrimaryKeyLetterCase());
changesList.add(change);
} else {
// List the modified rows
if (!row.hasValues(rowAtEndPoint)) {
// If at least one value in the rows is different, add the change
Change change = createModificationChange(dataAtStartPoint.getDataType(), dataName, row, rowAtEndPoint,
getTableLetterCase(), getColumnLetterCase(), getPrimaryKeyLetterCase());
changesList.add(change);
}
}
}
return changesList;
}
/**
* Returns the list of changes for the data when there is no primary key.
*
* @param dataName The name of the data.
* @param dataAtStartPoint The data at start point.
* @param dataAtEndPoint The data at end point.
* @return The list of changes for the data.
*/
private List<Change> getChangesListWithoutPks(String dataName, AbstractDbData<?> dataAtStartPoint,
AbstractDbData<?> dataAtEndPoint) {
List<Change> changesList = new ArrayList<>();
// List the created rows : the row is not present at the start point
List<Row> rowsAtStartPointList = new ArrayList<>(dataAtStartPoint.getRowsList());
for (Row rowAtEndPoint : dataAtEndPoint.getRowsList()) {
int index = -1;
int index1 = 0;
for (Row rowAtStartPoint : rowsAtStartPointList) {
if (rowAtEndPoint.hasValues(rowAtStartPoint)) {
index = index1;
break;
}
index1++;
}
if (index == -1) {
Change change = createCreationChange(dataAtStartPoint.getDataType(), dataName, rowAtEndPoint,
getTableLetterCase(), getColumnLetterCase(), getPrimaryKeyLetterCase());
changesList.add(change);
} else {
rowsAtStartPointList.remove(index);
}
}
// List the deleted rows : the row is not present at the end point
List<Row> rowsAtEndPointList = new ArrayList<>(dataAtEndPoint.getRowsList());
for (Row rowAtStartPoint : dataAtStartPoint.getRowsList()) {
int index = -1;
int index1 = 0;
for (Row rowAtEndPoint : rowsAtEndPointList) {
if (rowAtStartPoint.hasValues(rowAtEndPoint)) {
index = index1;
break;
}
index1++;
}
if (index == -1) {
Change change = createDeletionChange(dataAtStartPoint.getDataType(), dataName, rowAtStartPoint,
getTableLetterCase(), getColumnLetterCase(), getPrimaryKeyLetterCase());
changesList.add(change);
} else {
rowsAtEndPointList.remove(index);
}
}
return changesList;
}
/**
* Returns the list of changes for the data.
*
* @param dataName The name of the data.
* @param dataAtStartPoint The data at start point.
* @param dataAtEndPoint The data at end point.
* @return The list of changes for the data.
*/
private List<Change> getChangesList(String dataName, AbstractDbData<?> dataAtStartPoint,
AbstractDbData<?> dataAtEndPoint) {
if (dataAtStartPoint.getPksNameList().size() > 0) {
return getChangesListWithPks(dataName, dataAtStartPoint, dataAtEndPoint);
} else {
return getChangesListWithoutPks(dataName, dataAtStartPoint, dataAtEndPoint);
}
}
/**
* Returns the list of the changes.
*
* @return The list of the changes.
* @throws AssertJDBException If the changes are on all the tables and if the number of tables change between the
* start point and the end point. It is normally impossible.
*/
public List<Change> getChangesList() {
if (changesList == null) {
if (requestAtEndPoint == null && tablesAtEndPointList == null) {
throw new AssertJDBException("End point must be set before");
}
if (requestAtEndPoint != null) {
changesList = getChangesList(requestAtStartPoint.getRequest(), requestAtStartPoint, requestAtEndPoint);
} else {
changesList = new ArrayList<>();
Iterator<Table> iteratorAtStartPoint = tablesAtStartPointList.iterator();
Iterator<Table> iteratorAtEndPoint = tablesAtEndPointList.iterator();
while (iteratorAtStartPoint.hasNext()) {
Table tableAtStartPoint = iteratorAtStartPoint.next();
Table tableAtEndPoint = iteratorAtEndPoint.next();
changesList.addAll(getChangesList(tableAtStartPoint.getName(), tableAtStartPoint, tableAtEndPoint));
}
}
}
Collections.sort(changesList, ChangeComparator.INSTANCE);
return changesList;
}
/**
* Returns {@code Changes} only on the table name in parameter.
* @param tableName The table name
* @return {@code Changes} instance.
*/
public Changes getChangesOfTable(String tableName) {
if (tableName == null) {
throw new NullPointerException("tableName must be not null");
}
Changes changes = createChangesFromThis();
List<Change> changesList = getChangesList();
if (tablesList != null) {
for (Change change : changesList) {
if (getTableLetterCase().isEqual(tableName, change.getDataName())) {
changes.changesList.add(change);
}
}
}
return changes;
}
/**
* Returns {@code Changes} only on the change type in parameter.
* @param changeType The change type
* @return {@code Changes} instance.
*/
public Changes getChangesOfType(ChangeType changeType) {
if (changeType == null) {
throw new NullPointerException("changeType must be not null");
}
Changes changes = createChangesFromThis();
List<Change> changesList = getChangesList();
for (Change change : changesList) {
if (changeType.equals(change.getChangeType())) {
changes.changesList.add(change);
}
}
return changes;
}
/**
* Creates a new instance of {@code Changes} from {@code this} one.
* @return The new instance.
*/
private Changes createChangesFromThis() {
Changes changes = new Changes();
if (request != null) {
changes.request = getDuplicatedRequest(request);
}
if (tablesList != null) {
changes.tablesList = new ArrayList<>();
for (Table table : tablesList) {
changes.tablesList.add(getDuplicatedTable(table));
}
}
changes.changesList = new ArrayList<>();
return changes;
}
}