DBMigrationAction.java
/*
* Copyright 2013 Gregory Graham.
*
* 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.
*/
package nz.co.gregs.dbvolution.actions;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.ArrayList;
import nz.co.gregs.dbvolution.databases.DBDatabase;
import nz.co.gregs.dbvolution.DBMigration;
import nz.co.gregs.dbvolution.DBQuery;
import nz.co.gregs.dbvolution.DBRow;
import nz.co.gregs.dbvolution.databases.DBStatement;
import nz.co.gregs.dbvolution.databases.QueryIntention;
import nz.co.gregs.dbvolution.databases.definitions.DBDefinition;
import nz.co.gregs.dbvolution.datatypes.DBLargeObject;
import nz.co.gregs.dbvolution.datatypes.QueryableDatatype;
import nz.co.gregs.dbvolution.exceptions.UnableToAccessDBMigrationFieldException;
import nz.co.gregs.dbvolution.expressions.DBExpression;
import nz.co.gregs.dbvolution.internal.properties.PropertyWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Provides support for the abstract concept of migrating rows using one or more
* tables to another table.
*
* @author Gregory Graham
* @param <R> the resulting DBRow using this DBMigrationAction
*/
public class DBMigrationAction<R extends DBRow> extends DBAction {
private static final long serialVersionUID = 1l;
private static final Log LOG = LogFactory.getLog(DBQueryInsertAction.class);
private final DBMigration<R> sourceMigration;
private final DBRow[] extraExamples;
/**
* Creates a DBMigrate action for the row.
*
* @param migration the mapping to transform the source data
* @param resultRow the resulting DBRow produced by the mapping
* @param examples extra examples used to reduce the source data set.
*/
public DBMigrationAction(DBMigration<R> migration, DBRow resultRow, DBRow... examples) {
super(resultRow, QueryIntention.MIGRATION);
sourceMigration = migration;
extraExamples = examples;
}
/**
* Perform the migration
*
* @param database the database used by this action
* @return a DBActionList of the migration's effects
* @throws SQLException SQL Exceptions may be thrown
*/
public DBActionList migrate(DBDatabase database) throws SQLException {
DBMigrationAction<R> migrate = new DBMigrationAction<>(sourceMigration, getRow());
final DBActionList executedActions = database.executeDBAction(migrate);
return executedActions;
}
@Override
@SuppressWarnings("unchecked")
public ArrayList<String> getSQLStatements(DBDatabase db) {
DBRow table = getRow();
DBDefinition defn = db.getDefinition();
String allColumns = processAllFieldsForMigration(db, (R) getRow());
ArrayList<String> strs = new ArrayList<>();
strs.add(defn.beginInsertLine()
+ defn.formatTableName(table)
+ defn.beginInsertColumnList()
+ allColumns
+ defn.endInsertColumnList()
+ getSQLForExtractionQuery(db));
return strs;
}
public String getSQLForExtractionQuery(DBDatabase database) {
DBQuery query = database.getDBQuery();
query.setBlankQueryAllowed(sourceMigration.getBlankQueryAllowed());
query.setCartesianJoinsAllowed(sourceMigration.getCartesianJoinsAllowed());
addTablesAndExpressions(query);
query.addExtraExamples(extraExamples);
query.setSortOrder(sourceMigration.getSortColumns());
return query.getSQLForQuery();
}
public void addTablesAndExpressions(DBQuery query) {
final DBRow mapper = sourceMigration.getMapper();
var optionalTables = sourceMigration.getOptionalTables();
Field[] fields = mapper.getClass().getFields();
if (fields.length == 0) {
throw new UnableToAccessDBMigrationFieldException(this, null);
}
for (Field field : fields) {
field.setAccessible(true);
final Object value;
try {
value = field.get(mapper);
if (value != null && DBRow.class.isAssignableFrom(value.getClass())) {
if (value instanceof DBRow) {
final DBRow dbRow = (DBRow) value;
dbRow.removeAllFieldsFromResults();
if (optionalTables.contains(dbRow)) {
query.addOptional(dbRow);
} else {
query.add(dbRow);
}
}
} else if (value != null && QueryableDatatype.class.isAssignableFrom(value.getClass())) {
final QueryableDatatype<?> qdtValue = (QueryableDatatype) value;
if ((value instanceof QueryableDatatype) && qdtValue.hasColumnExpression()) {
query.addExpressionColumn(value, qdtValue);
final DBExpression[] columnExpressions = qdtValue.getColumnExpression();
for (DBExpression columnExpression : columnExpressions) {
if (!columnExpression.isAggregator()) {
query.addGroupByColumn(value, columnExpression);
}
}
}
}
} catch (IllegalArgumentException | IllegalAccessException ex) {
throw new UnableToAccessDBMigrationFieldException(this, field, ex);
}
}
}
@Override
public DBActionList execute(DBDatabase db) throws SQLException {
DBActionList actions = new DBActionList(new DBMigrationAction<>(sourceMigration, getRow(), extraExamples));
try (DBStatement statement = db.getDBStatement()) {
for (String sql : getSQLStatements(db)) {
try {
statement.execute("MIGRATION INSERT", QueryIntention.BULK_INSERT, sql);
} catch (SQLException sqlex) {
try {
statement.execute("MIGRATION INSERT", QueryIntention.BULK_INSERT, sql);
} catch (SQLException ex) {
throw new SQLException(ex.getLocalizedMessage() + ":" + sql, ex);
}
}
}
}
return actions;
}
private String processAllFieldsForMigration(DBDatabase database, R row) {
StringBuilder allColumns = new StringBuilder();
StringBuilder allValues = new StringBuilder();
StringBuilder allChangedColumns = new StringBuilder();
StringBuilder allSetValues = new StringBuilder();
DBDefinition defn = database.getDefinition();
var props = row.getColumnPropertyWrappers();
String allColumnSeparator = "";
String columnSeparator = "";
String valuesSeparator = defn.beginValueClause();
String allValuesSeparator = defn.beginValueClause();
for (var prop : props) {
// BLOBS are not inserted normally so don't include them
if (prop.isColumn()) {
final QueryableDatatype<?> qdt = prop.getQueryableDatatype();
if (qdt != null) {
if (!(qdt instanceof DBLargeObject)) {
//support for inserting empty rows in a table with an autoincrementing pk
if (!prop.isAutoIncrement()) {
allColumns
.append(allColumnSeparator)
.append(" ")
.append(defn.formatColumnName(prop.columnName()));
allColumnSeparator = defn.getValuesClauseColumnSeparator();
// add the value
allValues
.append(allValuesSeparator)
.append(qdt.toSQLString(database.getDefinition()));
allValuesSeparator = defn.getValuesClauseValueSeparator();
}
if (qdt.hasBeenSet()) {
// nice normal columns
// Add the column
allChangedColumns
.append(columnSeparator)
.append(" ")
.append(defn.formatColumnName(prop.columnName()));
columnSeparator = defn.getValuesClauseColumnSeparator();
// add the value
allSetValues
.append(valuesSeparator)
.append(qdt.toSQLString(database.getDefinition()));
valuesSeparator = defn.getValuesClauseValueSeparator();
}
}
}
}
}
allValues.append(defn.endValueClause());
allSetValues.append(defn.endValueClause());
return allColumns.toString();
}
@Override
protected DBActionList getRevertDBActionList() {
throw new UnsupportedOperationException("Reverting A Migration Is Not Possible Yet.");
}
}