DBQueryInsertAction.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 nz.co.gregs.dbvolution.exceptions.FailedToExecuteDBQueryInsertException;
import java.sql.SQLException;
import java.util.ArrayList;
import nz.co.gregs.dbvolution.databases.DBDatabase;
import nz.co.gregs.dbvolution.DBQueryInsert;
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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Provides support for the abstract concept of migrating rows from one or more
 * tables to another table.
 *
 * @author Gregory Graham
 * @param <R> the resulting DBRow from this DBQueryInsertAction
 */
public class DBQueryInsertAction<R extends DBRow> extends DBAction {

	private static final long serialVersionUID = 1l;
	
	private static final Log LOG = LogFactory.getLog(DBQueryInsertAction.class);

	private final DBQueryInsert<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 DBQueryInsertAction(DBQueryInsert<R> migration, DBRow resultRow, DBRow... examples) {
		super(resultRow, QueryIntention.INSERT_QUERY);
		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 {
		DBQueryInsertAction<R> migrate = new DBQueryInsertAction<>(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()
				+ sourceMigration.getSQLForQuery(db, extraExamples));
		return strs;
	}

	@Override
	public DBActionList execute(DBDatabase db) throws SQLException {
		DBActionList actions = new DBActionList(new DBQueryInsertAction<>(sourceMigration, getRow(), extraExamples));

		try (DBStatement statement = db.getDBStatement()) {
			for (String sql : getSQLStatements(db)) {
				try {
					statement.execute("BULK INSERT", QueryIntention.BULK_INSERT,sql);
				} catch (SQLException sqlex) {
					try {
						statement.execute("BULK INSERT",QueryIntention.BULK_INSERT,sql);
					} catch (SQLException ex) {
						throw new FailedToExecuteDBQueryInsertException(sql, sqlex);
					}
				}
			}
		}
		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 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(defn));
						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(defn));
						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.");
	}

//	@Override
//	protected DBActionList getActions() {//DBRow row) {
//		return new DBActionList(new DBInsert(getRow()));
//	}
}