DBUpdateForcedOnSimpleTypesUsingPrimaryKey.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.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import nz.co.gregs.dbvolution.DBRow;
import nz.co.gregs.dbvolution.databases.DBDatabase;
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.expressions.BooleanExpression;

/**
 * Provides support for the abstract concept of updating rows without primary
 * keys.
 *
 * <p>
 * The best way to use this is by using {@link DBUpdate#getUpdates(nz.co.gregs.dbvolution.DBRow...)
 * } to automatically use this action.
 *
 * @author Gregory Graham
 */
public class DBUpdateForcedOnSimpleTypesUsingPrimaryKey extends DBUpdateSimpleTypes {

	private static final long serialVersionUID = 1l;

	DBUpdateForcedOnSimpleTypesUsingPrimaryKey(DBRow row) {
		super(row);
		var props = this.row.getNonPrimaryKeyNonDynamicPropertyWrappers();
		props.forEach(prop -> prop.getQueryableDatatype().setChanged());
	}

	/**
	 * Executes required update actions for the row and returns a
	 * {@link DBActionList} of those actions.The original rows are not changed by
	 * this method, or any DBUpdate method.
	 *
	 * Use {@link DBRow#setSimpleTypesToUnchanged() } if you need to ignore the
	 * changes to the row.
	 *
	 * @param db the target database
	 * @param row the row to be updated
	 * @return a list of the actions taken to action this update
	 * @throws SQLException database exceptions
	 */
	public static DBActionList updateAnyway(DBDatabase db, DBRow row) throws SQLException {
		DBActionList updates = getUpdateAnyways(row);
		for (DBAction act : updates) {
			db.executeDBAction(act);
		}
		return updates;
	}

	/**
	 * Creates a DBActionList of update actions for the rows.
	 *
	 * <p>
	 * The actions created can be applied on a particular database using
	 * {@link DBActionList#execute(nz.co.gregs.dbvolution.databases.DBDatabase)}
	 *
	 * @param rows the rows to be updated
	 * @return a DBActionList of updates.
	 * @throws SQLException database exceptions
	 */
	public static DBActionList getUpdateAnyways(DBRow... rows) throws SQLException {
		DBActionList updates = new DBActionList();
		for (DBRow row : rows) {
			final List<QueryableDatatype<?>> primaryKeys = row.getPrimaryKeys();
			if (primaryKeys == null || primaryKeys.isEmpty()) {
			} else {
				updates.add(new DBUpdateForcedOnSimpleTypesUsingPrimaryKey(row));
			}
			if (row.hasLargeObjects()) {
				updates.add(new DBUpdateLargeObjects(row));
			}
		}
		return updates;
	}

	@Override
	public List<String> getSQLStatements(DBDatabase db) {
		DBRow table = getRow();
		DBDefinition defn = db.getDefinition();

		StringBuilder sql = new StringBuilder()
				.append(defn.beginUpdateLine())
				.append(defn.formatTableName(table))
				.append(defn.beginSetClause())
				.append(getSetClause(db, table))
				.append(defn.beginWhereClause())
				.append(defn.getWhereClauseBeginningCondition());
		for (var prop : table.getPrimaryKeyPropertyWrappers()) {
			QueryableDatatype<?> qdt = prop.getQueryableDatatype();
			if (qdt.isNull()) {
				sql.append(defn.beginWhereClauseLine())
						.append(BooleanExpression.isNull(table.column(qdt)).toSQLString(defn));
			} else {
				sql.append(defn.beginWhereClauseLine())
						.append(prop.columnName())
						.append(defn.getEqualsComparator())
						.append(qdt.hasChanged() ? qdt.getPreviousSQLValue(defn) : qdt.toSQLString(defn));
			}
		}
		sql.append(defn.endDeleteLine());
		List<String> sqls = new ArrayList<>();
		sqls.add(sql.toString());
		return sqls;
	}

	/**
	 * Creates the required SET clause of the UPDATE statement.
	 *
	 * @param db the target database
	 * @param row the row to be updated
	 * <p style="color: #F90;">Support DBvolution at
	 * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
	 * @return The SET clause of the UPDATE statement.
	 */
	@Override
	protected String getSetClause(DBDatabase db, DBRow row) {
		DBDefinition defn = db.getDefinition();
		StringBuilder sql = new StringBuilder();
		var fields = row.getColumnPropertyWrappers();

		String separator = defn.getStartingSetSubClauseSeparator();
		for (var field : fields) {
			if (field.isColumn() && !field.isPrimaryKey()) {
				final QueryableDatatype<?> qdt = field.getQueryableDatatype();
				if (qdt != null) {
					if (!(qdt instanceof DBLargeObject)) {
						if (qdt.hasBeenSet()) {
							String columnName = field.columnName();
							sql.append(separator)
									.append(defn.formatColumnName(columnName))
									.append(defn.getEqualsComparator())
									.append(qdt
											.toSQLString(defn));
							separator = defn.getSubsequentSetSubClauseSeparator();
						} else if (qdt.hasDefaultUpdateValue()) {
							String columnName = field.columnName();
							sql.append(separator)
									.append(defn.formatColumnName(columnName))
									.append(defn.getEqualsComparator())
									.append(qdt.getDefaultUpdateValueSQLString(defn));
							separator = defn.getSubsequentSetSubClauseSeparator();
						}
					}
				}
			}
		}

		return sql.toString();
	}

	@Override
	protected DBActionList getRevertDBActionList() {
		DBActionList dbActionList = new DBActionList();
		dbActionList.add(new DBUpdateToPreviousValues(this.getRow()));
		return dbActionList;
	}

	@Override
	public DBActionList execute(DBDatabase db) throws SQLException {
		DBRow table = originalRow;
		DBActionList actions = new DBActionList(new DBUpdateForcedOnSimpleTypesUsingPrimaryKey(table));
		try ( DBStatement statement = db.getDBStatement()) {
			for (String sql : getSQLStatements(db)) {
				statement.execute("Update row", QueryIntention.UPDATE_ROW, sql);
			}
		}
		refetchIfClusterRequires(db, originalRow);
		return actions;
	}

}