DBMigrationAction.java

  1. /*
  2.  * Copyright 2013 Gregory Graham.
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *      http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16. package nz.co.gregs.dbvolution.actions;

  17. import java.lang.reflect.Field;
  18. import java.sql.SQLException;
  19. import java.util.ArrayList;
  20. import nz.co.gregs.dbvolution.databases.DBDatabase;
  21. import nz.co.gregs.dbvolution.DBMigration;
  22. import nz.co.gregs.dbvolution.DBQuery;
  23. import nz.co.gregs.dbvolution.DBRow;
  24. import nz.co.gregs.dbvolution.databases.DBStatement;
  25. import nz.co.gregs.dbvolution.databases.QueryIntention;
  26. import nz.co.gregs.dbvolution.databases.definitions.DBDefinition;
  27. import nz.co.gregs.dbvolution.datatypes.DBLargeObject;
  28. import nz.co.gregs.dbvolution.datatypes.QueryableDatatype;
  29. import nz.co.gregs.dbvolution.exceptions.UnableToAccessDBMigrationFieldException;
  30. import nz.co.gregs.dbvolution.expressions.DBExpression;
  31. import nz.co.gregs.dbvolution.internal.properties.PropertyWrapper;
  32. import org.apache.commons.logging.Log;
  33. import org.apache.commons.logging.LogFactory;

  34. /**
  35.  * Provides support for the abstract concept of migrating rows using one or more
  36.  * tables to another table.
  37.  *
  38.  * @author Gregory Graham
  39.  * @param <R> the resulting DBRow using this DBMigrationAction
  40.  */
  41. public class DBMigrationAction<R extends DBRow> extends DBAction {

  42.     private static final long serialVersionUID = 1l;

  43.     private static final Log LOG = LogFactory.getLog(DBQueryInsertAction.class);

  44.     private final DBMigration<R> sourceMigration;
  45.     private final DBRow[] extraExamples;

  46.     /**
  47.      * Creates a DBMigrate action for the row.
  48.      *
  49.      * @param migration the mapping to transform the source data
  50.      * @param resultRow the resulting DBRow produced by the mapping
  51.      * @param examples extra examples used to reduce the source data set.
  52.      */
  53.     public DBMigrationAction(DBMigration<R> migration, DBRow resultRow, DBRow... examples) {
  54.         super(resultRow, QueryIntention.MIGRATION);
  55.         sourceMigration = migration;
  56.         extraExamples = examples;
  57.     }

  58.     /**
  59.      * Perform the migration
  60.      *
  61.      * @param database the database used by this action
  62.      * @return a DBActionList of the migration's effects
  63.      * @throws SQLException SQL Exceptions may be thrown
  64.      */
  65.     public DBActionList migrate(DBDatabase database) throws SQLException {
  66.         DBMigrationAction<R> migrate = new DBMigrationAction<>(sourceMigration, getRow());
  67.         final DBActionList executedActions = database.executeDBAction(migrate);
  68.         return executedActions;
  69.     }

  70.     @Override
  71.     @SuppressWarnings("unchecked")
  72.     public ArrayList<String> getSQLStatements(DBDatabase db) {
  73.         DBRow table = getRow();
  74.         DBDefinition defn = db.getDefinition();
  75.         String allColumns = processAllFieldsForMigration(db, (R) getRow());

  76.         ArrayList<String> strs = new ArrayList<>();
  77.         strs.add(defn.beginInsertLine()
  78.                 + defn.formatTableName(table)
  79.                 + defn.beginInsertColumnList()
  80.                 + allColumns
  81.                 + defn.endInsertColumnList()
  82.                 + getSQLForExtractionQuery(db));
  83.         return strs;
  84.     }

  85.     public String getSQLForExtractionQuery(DBDatabase database) {
  86.         DBQuery query = database.getDBQuery();
  87.         query.setBlankQueryAllowed(sourceMigration.getBlankQueryAllowed());
  88.         query.setCartesianJoinsAllowed(sourceMigration.getCartesianJoinsAllowed());
  89.        
  90.         addTablesAndExpressions(query);
  91.         query.addExtraExamples(extraExamples);
  92.         query.setSortOrder(sourceMigration.getSortColumns());
  93.         return query.getSQLForQuery();
  94.     }
  95.    
  96.     public void addTablesAndExpressions(DBQuery query) {
  97.         final DBRow mapper = sourceMigration.getMapper();
  98.         var optionalTables = sourceMigration.getOptionalTables();
  99.         Field[] fields = mapper.getClass().getFields();
  100.         if (fields.length == 0) {
  101.             throw new UnableToAccessDBMigrationFieldException(this, null);
  102.         }
  103.         for (Field field : fields) {
  104.             field.setAccessible(true);
  105.             final Object value;
  106.             try {
  107.                 value = field.get(mapper);
  108.                 if (value != null && DBRow.class.isAssignableFrom(value.getClass())) {
  109.                     if (value instanceof DBRow) {
  110.                         final DBRow dbRow = (DBRow) value;
  111.                         dbRow.removeAllFieldsFromResults();
  112.                         if (optionalTables.contains(dbRow)) {
  113.                             query.addOptional(dbRow);
  114.                         } else {
  115.                             query.add(dbRow);
  116.                         }
  117.                     }
  118.                 } else if (value != null && QueryableDatatype.class.isAssignableFrom(value.getClass())) {
  119.                     final QueryableDatatype<?> qdtValue = (QueryableDatatype) value;
  120.                     if ((value instanceof QueryableDatatype) && qdtValue.hasColumnExpression()) {
  121.                         query.addExpressionColumn(value, qdtValue);
  122.                         final DBExpression[] columnExpressions = qdtValue.getColumnExpression();
  123.                         for (DBExpression columnExpression : columnExpressions) {
  124.                             if (!columnExpression.isAggregator()) {
  125.                                 query.addGroupByColumn(value, columnExpression);
  126.                             }
  127.                         }
  128.                     }
  129.                 }
  130.             } catch (IllegalArgumentException | IllegalAccessException ex) {
  131.                 throw new UnableToAccessDBMigrationFieldException(this, field, ex);
  132.             }
  133.         }
  134.     }

  135.     @Override
  136.     public DBActionList execute(DBDatabase db) throws SQLException {
  137.         DBActionList actions = new DBActionList(new DBMigrationAction<>(sourceMigration, getRow(), extraExamples));

  138.         try (DBStatement statement = db.getDBStatement()) {
  139.             for (String sql : getSQLStatements(db)) {
  140.                 try {
  141.                     statement.execute("MIGRATION INSERT", QueryIntention.BULK_INSERT, sql);
  142.                 } catch (SQLException sqlex) {
  143.                     try {
  144.                         statement.execute("MIGRATION INSERT", QueryIntention.BULK_INSERT, sql);
  145.                     } catch (SQLException ex) {
  146.                         throw new SQLException(ex.getLocalizedMessage() + ":" + sql, ex);
  147.                     }
  148.                 }
  149.             }
  150.         }
  151.         return actions;
  152.     }

  153.     private String processAllFieldsForMigration(DBDatabase database, R row) {
  154.         StringBuilder allColumns = new StringBuilder();
  155.         StringBuilder allValues = new StringBuilder();
  156.         StringBuilder allChangedColumns = new StringBuilder();
  157.         StringBuilder allSetValues = new StringBuilder();
  158.         DBDefinition defn = database.getDefinition();
  159.         var props = row.getColumnPropertyWrappers();
  160.         String allColumnSeparator = "";
  161.         String columnSeparator = "";
  162.         String valuesSeparator = defn.beginValueClause();
  163.         String allValuesSeparator = defn.beginValueClause();
  164.         for (var prop : props) {
  165.             // BLOBS are not inserted normally so don't include them
  166.             if (prop.isColumn()) {
  167.                 final QueryableDatatype<?> qdt = prop.getQueryableDatatype();
  168.                 if (qdt != null) {
  169.                     if (!(qdt instanceof DBLargeObject)) {
  170.                         //support for inserting empty rows in a table with an autoincrementing pk
  171.                         if (!prop.isAutoIncrement()) {
  172.                             allColumns
  173.                                     .append(allColumnSeparator)
  174.                                     .append(" ")
  175.                                     .append(defn.formatColumnName(prop.columnName()));
  176.                             allColumnSeparator = defn.getValuesClauseColumnSeparator();
  177.                             // add the value
  178.                             allValues
  179.                                     .append(allValuesSeparator)
  180.                                     .append(qdt.toSQLString(database.getDefinition()));
  181.                             allValuesSeparator = defn.getValuesClauseValueSeparator();
  182.                         }
  183.                         if (qdt.hasBeenSet()) {
  184.                             // nice normal columns
  185.                             // Add the column
  186.                             allChangedColumns
  187.                                     .append(columnSeparator)
  188.                                     .append(" ")
  189.                                     .append(defn.formatColumnName(prop.columnName()));
  190.                             columnSeparator = defn.getValuesClauseColumnSeparator();
  191.                             // add the value
  192.                             allSetValues
  193.                                     .append(valuesSeparator)
  194.                                     .append(qdt.toSQLString(database.getDefinition()));
  195.                             valuesSeparator = defn.getValuesClauseValueSeparator();
  196.                         }
  197.                     }
  198.                 }
  199.             }
  200.         }
  201.         allValues.append(defn.endValueClause());
  202.         allSetValues.append(defn.endValueClause());
  203.         return allColumns.toString();
  204.     }

  205.     @Override
  206.     protected DBActionList getRevertDBActionList() {
  207.         throw new UnsupportedOperationException("Reverting A Migration Is Not Possible Yet.");
  208.     }
  209. }