All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.liferay.change.tracking.internal.conflict.CTConflictChecker Maven / Gradle / Ivy

There is a newer version: 3.0.108
Show newest version
/**
 * SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
 * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
 */

package com.liferay.change.tracking.internal.conflict;

import com.liferay.change.tracking.conflict.CTEntryConflictHelper;
import com.liferay.change.tracking.conflict.ConflictInfo;
import com.liferay.change.tracking.constants.CTConstants;
import com.liferay.change.tracking.internal.CTRowUtil;
import com.liferay.change.tracking.internal.reference.TableJoinHolder;
import com.liferay.change.tracking.internal.reference.TableReferenceDefinitionManager;
import com.liferay.change.tracking.internal.reference.TableReferenceInfo;
import com.liferay.change.tracking.internal.resolver.ConstraintResolverContextImpl;
import com.liferay.change.tracking.internal.resolver.ConstraintResolverKey;
import com.liferay.change.tracking.model.CTEntry;
import com.liferay.change.tracking.model.CTEntryTable;
import com.liferay.change.tracking.service.CTEntryLocalService;
import com.liferay.change.tracking.spi.display.CTDisplayRenderer;
import com.liferay.change.tracking.spi.resolver.ConstraintResolver;
import com.liferay.osgi.service.tracker.collections.map.ServiceTrackerMap;
import com.liferay.petra.sql.dsl.Column;
import com.liferay.petra.sql.dsl.DSLQueryFactoryUtil;
import com.liferay.petra.sql.dsl.Table;
import com.liferay.petra.sql.dsl.ast.ASTNode;
import com.liferay.petra.sql.dsl.expression.Predicate;
import com.liferay.petra.sql.dsl.query.DSLQuery;
import com.liferay.petra.sql.dsl.query.JoinStep;
import com.liferay.petra.sql.dsl.query.WhereStep;
import com.liferay.petra.sql.dsl.spi.ast.DefaultASTNodeListener;
import com.liferay.petra.sql.dsl.spi.query.Join;
import com.liferay.petra.string.StringBundler;
import com.liferay.portal.dao.orm.common.SQLTransformer;
import com.liferay.portal.kernel.change.tracking.CTColumnResolutionType;
import com.liferay.portal.kernel.dao.jdbc.CurrentConnectionUtil;
import com.liferay.portal.kernel.dao.orm.ORMException;
import com.liferay.portal.kernel.dao.orm.Session;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.model.ClassName;
import com.liferay.portal.kernel.model.change.tracking.CTModel;
import com.liferay.portal.kernel.service.ClassNameLocalService;
import com.liferay.portal.kernel.service.change.tracking.CTService;
import com.liferay.portal.kernel.service.persistence.change.tracking.CTPersistence;

import java.io.Serializable;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author Preston Crary
 */
public class CTConflictChecker> {

	public CTConflictChecker(
		ClassNameLocalService classNameLocalService,
		ServiceTrackerMap>
			constraintResolverServiceTrackerMap,
		ServiceTrackerMap>
			ctDisplayRendererServiceTrackerMap,
		ServiceTrackerMap
			ctEntryConflictHelperServiceTrackerMap,
		CTEntryLocalService ctEntryLocalService, CTService ctService,
		long modelClassNameId, long sourceCTCollectionId,
		TableReferenceDefinitionManager tableReferenceDefinitionManager,
		long targetCTCollectionId) {

		_classNameLocalService = classNameLocalService;
		_constraintResolverServiceTrackerMap =
			constraintResolverServiceTrackerMap;
		_ctDisplayRendererServiceTrackerMap =
			ctDisplayRendererServiceTrackerMap;
		_ctEntryConflictHelperServiceTrackerMap =
			ctEntryConflictHelperServiceTrackerMap;
		_ctEntryLocalService = ctEntryLocalService;
		_ctService = ctService;
		_modelClassNameId = modelClassNameId;
		_sourceCTCollectionId = sourceCTCollectionId;
		_tableReferenceDefinitionManager = tableReferenceDefinitionManager;
		_targetCTCollectionId = targetCTCollectionId;
	}

	public void addCTEntry(CTEntry ctEntry) {
		if (ctEntry.getChangeType() ==
				CTConstants.CT_CHANGE_TYPE_MODIFICATION) {

			if (_modificationCTEntries == null) {
				_modificationCTEntries = new HashMap<>();
			}

			_modificationCTEntries.put(ctEntry.getModelClassPK(), ctEntry);
		}

		_ctEntries.add(ctEntry);
	}

	public List check() throws PortalException {
		return _ctService.updateWithUnsafeFunction(this::_check);
	}

	private List _check(CTPersistence ctPersistence)
		throws PortalException {

		Connection connection = CurrentConnectionUtil.getConnection(
			ctPersistence.getDataSource());

		Set primaryKeyNames = ctPersistence.getCTColumnNames(
			CTColumnResolutionType.PK);

		if (primaryKeyNames.size() != 1) {
			throw new IllegalArgumentException(
				StringBundler.concat(
					"{ctPersistence=", ctPersistence, ", primaryKeyNames=",
					primaryKeyNames, "}"));
		}

		Iterator iterator = primaryKeyNames.iterator();

		String primaryKeyName = iterator.next();

		List conflictInfos = new ArrayList<>();

		_checkAdditions(
			connection, ctPersistence, conflictInfos, primaryKeyName);

		_checkDeletions(
			connection, ctPersistence, conflictInfos, primaryKeyName);

		if (_modificationCTEntries != null) {
			_checkModifications(
				connection, ctPersistence, conflictInfos, primaryKeyName);
		}

		List uniqueIndexColumnNames =
			ctPersistence.getUniqueIndexColumnNames();

		if (!uniqueIndexColumnNames.isEmpty()) {
			for (String[] columnNames : uniqueIndexColumnNames) {
				_checkConstraint(
					connection, ctPersistence, conflictInfos, primaryKeyName,
					columnNames);
			}
		}

		_checkMissingRequirements(connection, ctPersistence, conflictInfos);

		_checkCTEntries(ctPersistence, conflictInfos);

		return conflictInfos;
	}

	private void _checkAdditions(
		Connection connection, CTPersistence ctPersistence,
		List conflictInfos, String primaryKeyName) {

		try (PreparedStatement preparedStatement = connection.prepareStatement(
				StringBundler.concat(
					"select publication.", primaryKeyName, " from ",
					ctPersistence.getTableName(),
					" publication inner join CTEntry on CTEntry.modelClassPK ",
					"= publication.", primaryKeyName,
					" where CTEntry.ctCollectionId = ", _sourceCTCollectionId,
					" and CTEntry.modelClassNameId = ", _modelClassNameId,
					" and CTEntry.changeType = ",
					CTConstants.CT_CHANGE_TYPE_ADDITION,
					" and publication.ctCollectionId = ",
					_targetCTCollectionId));
			ResultSet resultSet = preparedStatement.executeQuery()) {

			while (resultSet.next()) {
				conflictInfos.add(
					new AdditionConflictInfo(resultSet.getLong(1)));
			}
		}
		catch (SQLException sqlException) {
			throw new ORMException(sqlException);
		}
	}

	private void _checkConstraint(
			Connection connection, CTPersistence ctPersistence,
			List conflictInfos, String primaryKeyName,
			String[] columnNames)
		throws PortalException {

		String constraintConflictsSQL = CTRowUtil.getConstraintConflictsSQL(
			ctPersistence.getTableName(), primaryKeyName, columnNames,
			_sourceCTCollectionId, _targetCTCollectionId);

		List> nextPrimaryKeys =
			_getConflictingPrimaryKeys(connection, constraintConflictsSQL);

		if (nextPrimaryKeys.isEmpty()) {
			return;
		}

		ConstraintResolver constraintResolver =
			(ConstraintResolver)
				_constraintResolverServiceTrackerMap.getService(
					new ConstraintResolverKey(
						ctPersistence.getModelClass(), columnNames));

		if (constraintResolver == null) {
			StringBundler sb = new StringBundler(2 * columnNames.length);

			for (String columnName : columnNames) {
				sb.append(columnName);
				sb.append(", ");
			}

			sb.setIndex(sb.index() - 1);

			String columnNamesString = sb.toString();

			for (Map.Entry currentPrimaryKeys : nextPrimaryKeys) {
				conflictInfos.add(
					new DefaultConstraintConflictInfo(
						currentPrimaryKeys.getKey(),
						currentPrimaryKeys.getValue(), columnNamesString));
			}

			return;
		}

		ConstraintResolverContextImpl constraintResolverContextImpl =
			new ConstraintResolverContextImpl<>(
				_ctService, _sourceCTCollectionId, _targetCTCollectionId);

		Set> attemptedPrimaryKeys = new HashSet<>();
		Set> resolvedPrimaryKeys = new HashSet<>(
			nextPrimaryKeys);

		while (!nextPrimaryKeys.isEmpty()) {
			Map.Entry currentPrimaryKeys = nextPrimaryKeys.get(0);

			constraintResolverContextImpl.setPrimaryKeys(
				currentPrimaryKeys.getKey(), currentPrimaryKeys.getValue());

			constraintResolver.resolveConflict(constraintResolverContextImpl);

			Session session = ctPersistence.getCurrentSession();

			session.flush();

			session.clear();

			attemptedPrimaryKeys.add(currentPrimaryKeys);

			nextPrimaryKeys = _getConflictingPrimaryKeys(
				connection, constraintConflictsSQL);

			resolvedPrimaryKeys.addAll(nextPrimaryKeys);

			nextPrimaryKeys.removeAll(attemptedPrimaryKeys);
		}

		List> unresolvedPrimaryKeys =
			_getConflictingPrimaryKeys(connection, constraintConflictsSQL);

		resolvedPrimaryKeys.removeAll(unresolvedPrimaryKeys);

		for (Map.Entry currentPrimaryKeys : resolvedPrimaryKeys) {
			conflictInfos.add(
				new ConstraintResolverConflictInfo(
					constraintResolver, true, currentPrimaryKeys.getKey(),
					currentPrimaryKeys.getValue()));
		}

		if (unresolvedPrimaryKeys.isEmpty()) {
			return;
		}

		for (Map.Entry currentPrimaryKeys : unresolvedPrimaryKeys) {
			conflictInfos.add(
				new ConstraintResolverConflictInfo(
					constraintResolver, false, currentPrimaryKeys.getKey(),
					currentPrimaryKeys.getValue()));
		}
	}

	private void _checkCTEntries(
		CTPersistence ctPersistence, List conflictInfos) {

		Class clazz = ctPersistence.getModelClass();

		CTEntryConflictHelper ctEntryConflictHelper =
			_ctEntryConflictHelperServiceTrackerMap.getService(clazz.getName());

		if (ctEntryConflictHelper == null) {
			return;
		}

		for (CTEntry ctEntry : _ctEntries) {
			if (ctEntryConflictHelper.hasModificationConflict(
					ctEntry, _targetCTCollectionId)) {

				conflictInfos.add(
					new ModificationConflictInfo(
						ctEntry.getModelClassPK(), false));
			}

			if (ctEntryConflictHelper.hasDeletionModificationConflict(
					ctEntry, _targetCTCollectionId)) {

				conflictInfos.add(
					new DeletionModificationConflictInfo(
						ctEntry.getModelClassPK()));
			}
		}
	}

	private void _checkDeletions(
		Connection connection, CTPersistence ctPersistence,
		List conflictInfos, String primaryKeyName) {

		try (PreparedStatement preparedStatement = connection.prepareStatement(
				StringBundler.concat(
					"select publication.", primaryKeyName, " from ",
					ctPersistence.getTableName(),
					" publication inner join CTEntry on CTEntry.modelClassPK ",
					"= publication.", primaryKeyName,
					" where CTEntry.ctCollectionId = ", _sourceCTCollectionId,
					" and CTEntry.modelClassNameId = ", _modelClassNameId,
					" and CTEntry.changeType = ",
					CTConstants.CT_CHANGE_TYPE_DELETION,
					" and (publication.ctCollectionId = ",
					_targetCTCollectionId, " or publication.ctCollectionId = ",
					CTConstants.CT_COLLECTION_ID_PRODUCTION,
					") and CTEntry.modelMvccVersion != ",
					"publication.mvccVersion"));
			ResultSet resultSet = preparedStatement.executeQuery()) {

			while (resultSet.next()) {
				conflictInfos.add(
					new ModificationDeletionConflictInfo(resultSet.getLong(1)));
			}
		}
		catch (SQLException sqlException) {
			throw new ORMException(sqlException);
		}
	}

	private void _checkMissingRequirements(
			Connection connection, CTPersistence ctPersistence,
			List conflictInfos)
		throws PortalException {

		if (!_ctEntryLocalService.hasCTEntries(
				_sourceCTCollectionId, _modelClassNameId)) {

			return;
		}

		DSLQuery ctEntryDSLQuery = DSLQueryFactoryUtil.select(
			CTEntryTable.INSTANCE.modelClassPK
		).from(
			CTEntryTable.INSTANCE
		).where(
			CTEntryTable.INSTANCE.ctCollectionId.eq(
				_sourceCTCollectionId
			).and(
				CTEntryTable.INSTANCE.modelClassNameId.eq(_modelClassNameId)
			).and(
				CTEntryTable.INSTANCE.changeType.eq(
					CTConstants.CT_CHANGE_TYPE_ADDITION)
			)
		);

		Map> combinedTableReferenceInfos =
			_tableReferenceDefinitionManager.getCombinedTableReferenceInfos();

		TableReferenceInfo tableReferenceInfo =
			combinedTableReferenceInfos.get(_modelClassNameId);

		if (tableReferenceInfo == null) {
			throw new IllegalArgumentException(
				"No table reference definition for " +
					ctPersistence.getModelClass());
		}

		DSLQuery dslQuery = null;

		Map, List> parentTableJoinHoldersMap =
			tableReferenceInfo.getParentTableJoinHoldersMap();

		for (List tableJoinHolders :
				parentTableJoinHoldersMap.values()) {

			for (TableJoinHolder tableJoinHolder : tableJoinHolders) {
				if (tableJoinHolder.isReversed()) {
					continue;
				}

				DSLQuery nextDSLQuery = _getMissingRequirementsDSLQuery(
					ctEntryDSLQuery, tableJoinHolder);

				if (dslQuery == null) {
					dslQuery = nextDSLQuery;
				}
				else {
					dslQuery = dslQuery.union(nextDSLQuery);
				}
			}
		}

		if (dslQuery != null) {
			try (PreparedStatement preparedStatement = _getPreparedStatement(
					connection, dslQuery);
				ResultSet resultSet = preparedStatement.executeQuery()) {

				if (resultSet.next()) {
					long modelClassPK = resultSet.getLong(1);

					String tableName = resultSet.getString(2);

					ClassName className = _classNameLocalService.getClassName(
						_tableReferenceDefinitionManager.getClassNameId(
							tableName));

					String classNameValue = className.getValue();

					conflictInfos.add(
						new MissingRequirementConflictInfo(
							classNameValue, modelClassPK,
							_ctDisplayRendererServiceTrackerMap.getService(
								classNameValue)));
				}
			}
			catch (SQLException sqlException) {
				throw new ORMException(
					"Unable to execute query: " + dslQuery, sqlException);
			}
		}
	}

	private void _checkModifications(
		Connection connection, CTPersistence ctPersistence,
		List conflictInfos, String primaryKeyName) {

		List resolvedPrimaryKeys = _getModifiedPrimaryKeys(
			connection, ctPersistence, primaryKeyName, true);

		for (Long resolvedPrimaryKey : resolvedPrimaryKeys) {
			conflictInfos.add(
				new ModificationConflictInfo(resolvedPrimaryKey, true));
		}

		_resolveModificationConflicts(
			connection, ctPersistence, primaryKeyName, resolvedPrimaryKeys);

		List unresolvedPrimaryKeys = _getModifiedPrimaryKeys(
			connection, ctPersistence, primaryKeyName, false);

		for (Long unresolvedPrimaryKey : unresolvedPrimaryKeys) {
			conflictInfos.add(
				new ModificationConflictInfo(unresolvedPrimaryKey, false));
		}

		_updateModelMvccVersion(
			connection, primaryKeyName, ctPersistence.getTableName(),
			unresolvedPrimaryKeys);

		List deletionModificationPKs = _getDeletionModificationPKs(
			connection, ctPersistence, primaryKeyName);

		for (long deletionModificationPK : deletionModificationPKs) {
			conflictInfos.add(
				new DeletionModificationConflictInfo(deletionModificationPK));
		}
	}

	private List> _getConflictingPrimaryKeys(
		Connection connection, String constraintConflictsSQL) {

		Set ignorablePrimaryKeys = new HashSet<>();

		for (CTEntry ctEntry :
				_ctEntryLocalService.getCTEntries(
					_sourceCTCollectionId, _modelClassNameId)) {

			if (ctEntry.getChangeType() !=
					CTConstants.CT_CHANGE_TYPE_ADDITION) {

				ignorablePrimaryKeys.add(ctEntry.getModelClassPK());
			}
		}

		try (PreparedStatement preparedStatement = connection.prepareStatement(
				constraintConflictsSQL);
			ResultSet resultSet = preparedStatement.executeQuery()) {

			List> primaryKeys = null;

			while (resultSet.next()) {
				long sourcePK = resultSet.getLong(1);
				long targetPK = resultSet.getLong(2);

				if (ignorablePrimaryKeys.contains(sourcePK) ||
					ignorablePrimaryKeys.contains(targetPK)) {

					continue;
				}

				if (primaryKeys == null) {
					primaryKeys = new ArrayList<>();
				}

				primaryKeys.add(
					new AbstractMap.SimpleImmutableEntry<>(sourcePK, targetPK));
			}

			if (primaryKeys == null) {
				primaryKeys = Collections.emptyList();
			}

			return primaryKeys;
		}
		catch (SQLException sqlException) {
			throw new ORMException(sqlException);
		}
	}

	private List _getDeletionModificationPKs(
		Connection connection, CTPersistence ctPersistence,
		String primaryKeyName) {

		try (PreparedStatement preparedStatement = connection.prepareStatement(
				StringBundler.concat(
					"select CTEntry.modelClassPK from CTEntry left join ",
					ctPersistence.getTableName(), " publication on ",
					"publication.", primaryKeyName, " = CTEntry.modelClassPK ",
					"and (publication.ctCollectionId = ", _targetCTCollectionId,
					" or publication.ctCollectionId = ",
					CTConstants.CT_COLLECTION_ID_PRODUCTION,
					") where CTEntry.ctCollectionId = ", _sourceCTCollectionId,
					" and CTEntry.modelClassNameId = ", _modelClassNameId,
					" and CTEntry.changeType = ",
					CTConstants.CT_CHANGE_TYPE_MODIFICATION, " and ",
					"publication.", primaryKeyName, " is null"));
			ResultSet resultSet = preparedStatement.executeQuery()) {

			List primaryKeys = new ArrayList<>();

			while (resultSet.next()) {
				primaryKeys.add(resultSet.getLong(1));
			}

			return primaryKeys;
		}
		catch (SQLException sqlException) {
			throw new ORMException(sqlException);
		}
	}

	private DSLQuery _getMissingRequirementsDSLQuery(
		DSLQuery ctEntryDSLQuery, TableJoinHolder tableJoinHolder) {

		WhereStep whereStep = tableJoinHolder.getMissingRequirementWhereStep();

		Deque joins = new LinkedList<>();

		ASTNode astNode = whereStep;

		while (astNode instanceof Join) {
			Join join = (Join)astNode;

			joins.push(join);

			astNode = join.getChild();
		}

		Join join = null;

		JoinStep joinStep = (JoinStep)astNode;

		while ((join = joins.poll()) != null) {
			Predicate predicate = join.getOnPredicate();

			Table table = join.getTable();

			predicate = predicate.and(
				() -> {
					Column ctCollectionIdColumn = table.getColumn(
						"ctCollectionId", Long.class);

					if (ctCollectionIdColumn != null) {
						if (_targetCTCollectionId ==
								CTConstants.CT_COLLECTION_ID_PRODUCTION) {

							return ctCollectionIdColumn.in(
								new Long[] {
									_sourceCTCollectionId, _targetCTCollectionId
								});
						}

						return ctCollectionIdColumn.in(
							new Long[] {
								_sourceCTCollectionId, _targetCTCollectionId,
								CTConstants.CT_COLLECTION_ID_PRODUCTION
							});
					}

					return null;
				});

			joinStep = joinStep.leftJoinOn(table, predicate);
		}

		Column childPKColumn = tableJoinHolder.getChildPKColumn();

		Table childTable = childPKColumn.getTable();

		Column ctCollectionIdColumn = childTable.getColumn(
			"ctCollectionId", Long.class);

		Predicate missingRequirementWherePredicate =
			tableJoinHolder.getMissingRequirementWherePredicate();

		return joinStep.where(
			missingRequirementWherePredicate.and(
				childPKColumn.in(
					ctEntryDSLQuery
				).and(
					ctCollectionIdColumn.eq(_sourceCTCollectionId)
				)));
	}

	private List _getModifiedPrimaryKeys(
		Connection connection, CTPersistence ctPersistence,
		String primaryKeyName, boolean resolved) {

		Set strictColumnNames = ctPersistence.getCTColumnNames(
			CTColumnResolutionType.STRICT);

		StringBundler sb = new StringBundler();

		sb.append("select publication.");
		sb.append(primaryKeyName);
		sb.append(" from ");
		sb.append(ctPersistence.getTableName());
		sb.append(" publication inner join ");
		sb.append(ctPersistence.getTableName());
		sb.append(" production on publication.");
		sb.append(primaryKeyName);
		sb.append(" = production.");
		sb.append(primaryKeyName);
		sb.append(" and publication.ctCollectionId = ");
		sb.append(_sourceCTCollectionId);
		sb.append(" and production.ctCollectionId = ");
		sb.append(_targetCTCollectionId);
		sb.append(" inner join CTEntry ctEntry on ctEntry.ctCollectionId = ");
		sb.append(_sourceCTCollectionId);
		sb.append(" and ctEntry.modelClassNameId = ");
		sb.append(_modelClassNameId);
		sb.append(" and ctEntry.modelClassPK = production.");
		sb.append(primaryKeyName);
		sb.append(" and ctEntry.changeType = ");
		sb.append(CTConstants.CT_CHANGE_TYPE_MODIFICATION);
		sb.append(" and ctEntry.modelMvccVersion != production.mvccVersion");

		Map columnsMap = new HashMap<>(
			ctPersistence.getTableColumnsMap());

		Set columnNames = columnsMap.keySet();

		columnNames.retainAll(strictColumnNames);

		Collection columnTypes = columnsMap.values();

		String andOr = " or ";
		String comparison = " != ";

		if (resolved) {
			andOr = " and ";
			comparison = " = ";
		}

		if (!columnTypes.contains(Types.BLOB)) {
			sb.append(" where ");

			for (Map.Entry entry : columnsMap.entrySet()) {
				String conflictColumnName = entry.getKey();

				sb.append("((");

				if (entry.getValue() == Types.CLOB) {
					sb.append("CAST_CLOB_TEXT(publication.");
					sb.append(conflictColumnName);
					sb.append(")");
					sb.append(comparison);
					sb.append("CAST_CLOB_TEXT(production.");
					sb.append(conflictColumnName);
					sb.append(")");
				}
				else {
					sb.append("publication.");
					sb.append(conflictColumnName);
					sb.append(comparison);
					sb.append("production.");
					sb.append(conflictColumnName);
				}

				sb.append(") or (publication.");
				sb.append(conflictColumnName);
				sb.append(" is null and production.");
				sb.append(conflictColumnName);

				if (!resolved) {
					sb.append(" is not null) or (publication.");
					sb.append(conflictColumnName);
					sb.append(" is not null and production.");
					sb.append(conflictColumnName);
				}

				sb.append(" is null))");

				sb.append(andOr);
			}

			sb.setIndex(sb.index() - 1);
		}

		try (PreparedStatement preparedStatement = connection.prepareStatement(
				SQLTransformer.transform(sb.toString()));
			ResultSet resultSet = preparedStatement.executeQuery()) {

			List primaryKeys = new ArrayList<>();

			while (resultSet.next()) {
				long primaryKey = resultSet.getLong(1);

				primaryKeys.add(primaryKey);
			}

			return primaryKeys;
		}
		catch (SQLException sqlException) {
			throw new ORMException(sqlException);
		}
	}

	private PreparedStatement _getPreparedStatement(
			Connection connection, DSLQuery dslQuery)
		throws SQLException {

		DefaultASTNodeListener defaultASTNodeListener =
			new DefaultASTNodeListener();

		PreparedStatement preparedStatement = connection.prepareStatement(
			SQLTransformer.transform(dslQuery.toSQL(defaultASTNodeListener)));

		List scalarValues = defaultASTNodeListener.getScalarValues();

		for (int i = 0; i < scalarValues.size(); i++) {
			preparedStatement.setObject(i + 1, scalarValues.get(i));
		}

		return preparedStatement;
	}

	private void _resolveModificationConflicts(
		Connection connection, CTPersistence ctPersistence,
		String primaryKeyName, List resolvedPrimaryKeys) {

		if (resolvedPrimaryKeys.isEmpty()) {
			return;
		}

		long tempCTCollectionId = -_sourceCTCollectionId;

		StringBundler sb = new StringBundler(
			(2 * resolvedPrimaryKeys.size()) + 9);

		sb.append("update ");
		sb.append(ctPersistence.getTableName());
		sb.append(" set ctCollectionId = ");
		sb.append(tempCTCollectionId);
		sb.append(" where ctCollectionId = ");
		sb.append(_sourceCTCollectionId);
		sb.append(" and ");
		sb.append(primaryKeyName);
		sb.append(" in (");

		for (Long primaryKey : resolvedPrimaryKeys) {
			sb.append(primaryKey);
			sb.append(", ");
		}

		sb.setStringAt(")", sb.index() - 1);

		try (PreparedStatement preparedStatement = connection.prepareStatement(
				sb.toString())) {

			preparedStatement.executeUpdate();
		}
		catch (SQLException sqlException) {
			throw new ORMException(sqlException);
		}

		sb = new StringBundler("select ");

		Map tableColumnsMap =
			ctPersistence.getTableColumnsMap();

		Set ignoredColumnNames = ctPersistence.getCTColumnNames(
			CTColumnResolutionType.IGNORE);

		Set maxColumnNames = ctPersistence.getCTColumnNames(
			CTColumnResolutionType.MAX);

		Set minColumnNames = ctPersistence.getCTColumnNames(
			CTColumnResolutionType.MIN);

		for (String name : tableColumnsMap.keySet()) {
			if (name.equals("ctCollectionId")) {
				sb.append(_sourceCTCollectionId);
				sb.append(" as ");
			}
			else if (name.equals("mvccVersion")) {
				sb.append("(publication.mvccVersion + 1) ");
			}
			else if (ignoredColumnNames.contains(name)) {
				sb.append("production.");
			}
			else if (maxColumnNames.contains(name) ||
					 minColumnNames.contains(name)) {

				sb.append("composite.");
			}
			else {
				sb.append("publication.");
			}

			sb.append(name);
			sb.append(", ");
		}

		sb.setStringAt(" from ", sb.index() - 1);

		sb.append(ctPersistence.getTableName());
		sb.append(" production inner join ");
		sb.append(ctPersistence.getTableName());
		sb.append(" publication on production.");
		sb.append(primaryKeyName);
		sb.append(" = publication.");
		sb.append(primaryKeyName);

		if (!maxColumnNames.isEmpty() || !minColumnNames.isEmpty()) {
			sb.append(" inner join (select ");
			sb.append(primaryKeyName);

			for (String maxColumnName : maxColumnNames) {
				sb.append(", max(");
				sb.append(maxColumnName);
				sb.append(") ");
				sb.append(maxColumnName);
			}

			for (String minColumnName : minColumnNames) {
				sb.append(", min(");
				sb.append(minColumnName);
				sb.append(") ");
				sb.append(minColumnName);
			}

			sb.append(" from ");
			sb.append(ctPersistence.getTableName());
			sb.append(" where ctCollectionId in (");
			sb.append(_targetCTCollectionId);
			sb.append(", ");
			sb.append(tempCTCollectionId);
			sb.append(") group by ");
			sb.append(primaryKeyName);
			sb.append(") composite on composite.");
			sb.append(primaryKeyName);
			sb.append(" = production.");
			sb.append(primaryKeyName);
		}

		sb.append(" where publication.ctCollectionId = ");
		sb.append(tempCTCollectionId);
		sb.append(" and production.ctCollectionId = ");
		sb.append(_targetCTCollectionId);

		try {
			CTRowUtil.copyCTRows(ctPersistence, connection, sb.toString());
		}
		catch (SQLException sqlException) {
			throw new ORMException(sqlException);
		}

		try (PreparedStatement preparedStatement = connection.prepareStatement(
				StringBundler.concat(
					"delete from ", ctPersistence.getTableName(),
					" where ctCollectionId = ", tempCTCollectionId))) {

			preparedStatement.executeUpdate();
		}
		catch (SQLException sqlException) {
			throw new ORMException(sqlException);
		}
	}

	private void _updateModelMvccVersion(
		Connection connection, String primaryKeyName, String tableName,
		List unresolvedPrimaryKeys) {

		StringBundler sb = new StringBundler(
			(2 * unresolvedPrimaryKeys.size()) + 18);

		sb.append("select publication.");
		sb.append(primaryKeyName);
		sb.append(", publication.mvccVersion from ");
		sb.append(tableName);
		sb.append(" publication inner join CTEntry on publication.");
		sb.append(primaryKeyName);
		sb.append(" = CTEntry.modelClassPK and CTEntry.changeType = ");
		sb.append(CTConstants.CT_CHANGE_TYPE_MODIFICATION);
		sb.append(" and CTEntry.ctCollectionId = ");
		sb.append(_sourceCTCollectionId);
		sb.append(" and CTEntry.modelClassNameId = ");
		sb.append(_modelClassNameId);
		sb.append(" and publication.mvccVersion != CTEntry.modelMvccVersion");
		sb.append(" where publication.ctCollectionId = ");
		sb.append(_targetCTCollectionId);

		if (!unresolvedPrimaryKeys.isEmpty()) {
			sb.append(" and publication.");
			sb.append(primaryKeyName);
			sb.append(" not in (");

			for (Long unresolvedPrimaryKey : unresolvedPrimaryKeys) {
				sb.append(unresolvedPrimaryKey);
				sb.append(", ");
			}

			sb.setStringAt(")", sb.index() - 1);
		}

		try (PreparedStatement preparedStatement = connection.prepareStatement(
				sb.toString());
			ResultSet resultSet = preparedStatement.executeQuery()) {

			while (resultSet.next()) {
				long pk = resultSet.getLong(1);

				CTEntry ctEntry = _modificationCTEntries.get(pk);

				if (ctEntry != null) {
					long mvccVersion = resultSet.getLong(2);

					ctEntry.setModifiedDate(ctEntry.getModifiedDate());
					ctEntry.setModelMvccVersion(mvccVersion);

					_ctEntryLocalService.updateCTEntry(ctEntry);
				}
			}
		}
		catch (SQLException sqlException) {
			throw new ORMException(sqlException);
		}
	}

	private final ClassNameLocalService _classNameLocalService;
	private final ServiceTrackerMap
		>
			_constraintResolverServiceTrackerMap;
	private final ServiceTrackerMap>
		_ctDisplayRendererServiceTrackerMap;
	private final Set _ctEntries = new HashSet<>();
	private final ServiceTrackerMap
		_ctEntryConflictHelperServiceTrackerMap;
	private final CTEntryLocalService _ctEntryLocalService;
	private final CTService _ctService;
	private final long _modelClassNameId;
	private Map _modificationCTEntries;
	private final long _sourceCTCollectionId;
	private final TableReferenceDefinitionManager
		_tableReferenceDefinitionManager;
	private final long _targetCTCollectionId;

}