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

com.liferay.change.tracking.internal.reference.TableJoinHolderFactory Maven / Gradle / Ivy

There is a newer version: 3.0.107
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.reference;

import com.liferay.change.tracking.spi.reference.TableReferenceDefinition;
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.ast.ASTNodeListener;
import com.liferay.petra.sql.dsl.expression.Expression;
import com.liferay.petra.sql.dsl.expression.Predicate;
import com.liferay.petra.sql.dsl.query.DSLQuery;
import com.liferay.petra.sql.dsl.query.FromStep;
import com.liferay.petra.sql.dsl.query.JoinStep;
import com.liferay.petra.sql.dsl.query.WhereStep;
import com.liferay.petra.sql.dsl.spi.expression.DSLFunction;
import com.liferay.petra.sql.dsl.spi.expression.DefaultPredicate;
import com.liferay.petra.sql.dsl.spi.expression.Operand;
import com.liferay.petra.sql.dsl.spi.expression.Scalar;
import com.liferay.petra.sql.dsl.spi.query.From;
import com.liferay.petra.sql.dsl.spi.query.Join;
import com.liferay.petra.sql.dsl.spi.query.JoinType;
import com.liferay.petra.string.StringBundler;
import com.liferay.petra.string.StringPool;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author Preston Crary
 */
public class TableJoinHolderFactory {

	public static > TableJoinHolder create(
		Function joinFunction, boolean parent,
		Column primaryKeyColumn,
		TableReferenceDefinition tableReferenceDefinition) {

		JoinStep joinStep = joinFunction.apply(_validationFromStep);

		if (!(joinStep instanceof Join)) {
			throw new IllegalArgumentException(
				StringBundler.concat("Missing join in \"", joinStep, "\""));
		}

		JoinStepASTNodeListener joinStepASTNodeListener =
			new JoinStepASTNodeListener<>(tableReferenceDefinition.getTable());

		joinStep.toSQL(_emptyStringConsumer, joinStepASTNodeListener);

		if (joinStepASTNodeListener._fromTable == null) {
			throw new IllegalArgumentException(
				StringBundler.concat(
					"Join function must use provided from step for join step ",
					"\"", joinStep, "\""));
		}

		if (!joinStepASTNodeListener._hasRequiredTable) {
			throw new IllegalArgumentException(
				StringBundler.concat(
					"Required table \"", tableReferenceDefinition.getTable(),
					"\" is unused in join step \"", joinStep, "\""));
		}

		if (joinStepASTNodeListener._invalidJoin != null) {
			throw new IllegalArgumentException(
				StringBundler.concat(
					"Invalid join for join step \"", joinStep,
					"\", ensure table alias is used for self joins"));
		}

		if (joinStepASTNodeListener._invalidJoinOrder) {
			throw new IllegalArgumentException(
				StringBundler.concat(
					"First join must be on table \"",
					tableReferenceDefinition.getTable(), "\" for join step \"",
					joinStep, "\""));
		}

		if (joinStepASTNodeListener._invalidJoinType != null) {
			throw new IllegalArgumentException(
				StringBundler.concat(
					"Invalid join type \"",
					joinStepASTNodeListener._invalidJoinType,
					"\" for join step \"", joinStep, "\""));
		}

		if (joinStepASTNodeListener._invalidOperand != null) {
			throw new IllegalArgumentException(
				StringBundler.concat(
					"Invalid predicate operand \"",
					joinStepASTNodeListener._invalidOperand,
					"\" for join step \"", joinStep, "\""));
		}

		if (!joinStepASTNodeListener._tables.containsAll(
				joinStepASTNodeListener._columnTables)) {

			List> columnTables = new ArrayList<>(
				joinStepASTNodeListener._columnTables);

			Comparator> comparator = Comparator.comparing(
				Table::getName);

			columnTables.sort(comparator);

			List> joinTables = new ArrayList<>(
				joinStepASTNodeListener._tables);

			joinTables.sort(comparator);

			throw new IllegalArgumentException(
				StringBundler.concat(
					"Predicate column tables ", columnTables,
					" do not match join tables ", joinTables,
					" for join step \"", joinStep, "\""));
		}

		Column fromPKColumn = TableUtil.getPrimaryKeyColumn(
			joinStepASTNodeListener._fromTable);

		if (fromPKColumn == null) {
			throw new IllegalArgumentException(
				StringBundler.concat(
					"No long type primary key column found for table \"",
					joinStepASTNodeListener._fromTable, "\" for join step \"",
					joinStep, "\""));
		}

		WhereStep missingRequirementWhereStep = null;
		Predicate missingRequirementWherePredicate = null;

		if (parent) {
			missingRequirementWherePredicate = fromPKColumn.isNull();

			List bridgePredicates = _getBridgePredicates(
				joinStep);
			Set> childColumns = _getChildColumns(
				primaryKeyColumn.getTable(), joinStep);

			Iterator iterator = bridgePredicates.iterator();

			while (iterator.hasNext()) {
				BridgePredicate bridgePredicate = iterator.next();

				if (bridgePredicate.hasOnlyTable(primaryKeyColumn.getTable())) {
					missingRequirementWherePredicate =
						missingRequirementWherePredicate.and(
							bridgePredicate._predicate);

					iterator.remove();
				}
			}

			missingRequirementWhereStep = _getMissingRequirementWhereStep(
				primaryKeyColumn, fromPKColumn, bridgePredicates);

			for (Column column : childColumns) {
				Class clazz = column.getJavaType();

				missingRequirementWherePredicate =
					missingRequirementWherePredicate.and(
						column.isNotNull()
					).and(
						() -> {
							if (clazz == String.class) {
								Column stringColumn =
									(Column)column;

								return stringColumn.neq(StringPool.BLANK);
							}

							if (clazz == Long.class) {
								Column longColumn =
									(Column)column;

								return longColumn.neq(0L);
							}

							return null;
						}
					);
			}
		}

		return new TableJoinHolder(
			primaryKeyColumn, joinFunction, missingRequirementWherePredicate,
			missingRequirementWhereStep, fromPKColumn);
	}

	private static List _getBridgePredicates(
		JoinStep joinStep) {

		Queue defaultPredicateQueue = new LinkedList<>();
		Queue> expressionQueue = new LinkedList<>();
		List bridgePredicates = new LinkedList<>();

		ASTNode astNode = joinStep;

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

			defaultPredicateQueue.add((DefaultPredicate)join.getOnPredicate());

			DefaultPredicate defaultPredicate = null;

			while ((defaultPredicate = defaultPredicateQueue.poll()) != null) {
				Expression leftExpression =
					defaultPredicate.getLeftExpression();
				Expression rightExpression =
					defaultPredicate.getRightExpression();

				if (defaultPredicate.getOperand() == Operand.AND) {
					defaultPredicateQueue.add((DefaultPredicate)leftExpression);
					defaultPredicateQueue.add(
						(DefaultPredicate)rightExpression);
				}
				else {
					Set> tables = Collections.newSetFromMap(
						new IdentityHashMap<>());

					expressionQueue.add(leftExpression);
					expressionQueue.add(rightExpression);

					Expression expression = null;

					while ((expression = expressionQueue.poll()) != null) {
						if (expression instanceof Column) {
							Column column = (Column)expression;

							tables.add(column.getTable());
						}
						else if (expression instanceof DSLFunction) {
							DSLFunction dslFunction =
								(DSLFunction)expression;

							Collections.addAll(
								expressionQueue, dslFunction.getExpressions());
						}
					}

					bridgePredicates.add(
						new BridgePredicate(tables, defaultPredicate));
				}
			}

			astNode = join.getChild();
		}

		return bridgePredicates;
	}

	private static Set> _getChildColumns(
		Table table, JoinStep joinStep) {

		Set> childColumns = new HashSet<>();

		joinStep.toSQL(
			_emptyStringConsumer,
			astNode -> {
				if (astNode instanceof DefaultPredicate) {
					DefaultPredicate defaultPredicate =
						(DefaultPredicate)astNode;

					if (defaultPredicate.getOperand() == Operand.EQUAL) {
						Expression leftExpression =
							defaultPredicate.getLeftExpression();
						Expression rightExpression =
							defaultPredicate.getRightExpression();

						if ((leftExpression instanceof Column) &&
							!(rightExpression instanceof Scalar)) {

							Column column = (Column)leftExpression;

							if (column.getTable() == table) {
								childColumns.add(column);
							}
						}

						if (!(leftExpression instanceof Scalar) &&
							(rightExpression instanceof Column)) {

							Column column = (Column)rightExpression;

							if (column.getTable() == table) {
								childColumns.add(column);
							}
						}
					}
				}
			});

		return childColumns;
	}

	private static WhereStep _getMissingRequirementWhereStep(
		Column primaryKeyColumn, Column fromPKColumn,
		List bridgePredicates) {

		Table parentTable = fromPKColumn.getTable();

		JoinStep joinStep = DSLQueryFactoryUtil.select(
			primaryKeyColumn, new Scalar<>(parentTable.getTableName())
		).from(
			primaryKeyColumn.getTable()
		);

		Set> tables = Collections.newSetFromMap(
			new IdentityHashMap<>());

		tables.add(primaryKeyColumn.getTable());

		Set resolvedBridgePredicates = new HashSet<>();

		int previousSize = -1;

		while (resolvedBridgePredicates.size() != previousSize) {
			previousSize = resolvedBridgePredicates.size();

			for (BridgePredicate bridgePredicate : bridgePredicates) {
				if (resolvedBridgePredicates.contains(bridgePredicate)) {
					continue;
				}

				Table table = null;

				for (Table predicateTable : bridgePredicate._tables) {
					if (!tables.contains(predicateTable)) {
						if (table != null) {
							table = null;

							break;
						}

						table = predicateTable;
					}
				}

				if (table == null) {
					continue;
				}

				tables.add(table);

				Predicate predicate = null;

				for (BridgePredicate currentBridgePredicate :
						bridgePredicates) {

					if (resolvedBridgePredicates.contains(
							currentBridgePredicate)) {

						continue;
					}

					if (tables.containsAll(currentBridgePredicate._tables)) {
						resolvedBridgePredicates.add(currentBridgePredicate);

						if (predicate == null) {
							predicate = currentBridgePredicate._predicate;
						}
						else {
							predicate = predicate.and(
								currentBridgePredicate._predicate);
						}
					}
				}

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

		if (!resolvedBridgePredicates.containsAll(bridgePredicates)) {
			StringBundler sb = new StringBundler();

			sb.append("Unable to apply predicates [");

			for (BridgePredicate bridgePredicate : bridgePredicates) {
				if (!resolvedBridgePredicates.contains(bridgePredicate)) {
					sb.append(bridgePredicate._predicate);
					sb.append(", ");
				}
			}

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

			sb.append(joinStep);

			throw new IllegalArgumentException(sb.toString());
		}

		return joinStep;
	}

	private static final Consumer _emptyStringConsumer = string -> {
	};
	private static final FromStep _validationFromStep =
		new ValidationFromStep();
	private static final Set _validOperands = new HashSet<>(
		Arrays.asList(Operand.AND, Operand.EQUAL, Operand.LIKE));

	private static class BridgePredicate {

		public boolean hasOnlyTable(Table childTable) {
			for (Table table : _tables) {
				if (table != childTable) {
					return false;
				}
			}

			return true;
		}

		private BridgePredicate(Set> tables, Predicate predicate) {
			_tables = tables;
			_predicate = predicate;
		}

		private final Predicate _predicate;
		private final Set> _tables;

	}

	private static class JoinStepASTNodeListener>
		implements ASTNodeListener {

		@Override
		public void process(ASTNode astNode) {
			if (astNode instanceof Column) {
				Column column = (Column)astNode;

				_columnTables.add(column.getTable());

				if (!_hasRequiredTable &&
					Objects.equals(_table, column.getTable())) {

					_hasRequiredTable = true;
				}
			}
			else if (astNode instanceof DefaultPredicate) {
				DefaultPredicate defaultPredicate = (DefaultPredicate)astNode;

				Operand operand = defaultPredicate.getOperand();

				if (!_validOperands.contains(operand) &&
					(_invalidOperand == null)) {

					_invalidOperand = operand;
				}
			}
			else if (astNode instanceof From) {
				From from = (From)astNode;

				if (from.getChild() == _validationFromStep) {
					_fromTable = from.getTable();

					_tables.add(from.getTable());
				}
			}
			else if (astNode instanceof Join) {
				Join join = (Join)astNode;

				JoinType joinType = join.getJoinType();

				if (joinType != JoinType.INNER) {
					_invalidJoinType = joinType;
				}

				Table table = join.getTable();

				if ((_tables.size() == 1) && (table != _table)) {
					_invalidJoinOrder = true;
				}

				if (table.equals(_fromTable) &&
					Objects.equals(_fromTable.getName(), table.getName())) {

					_invalidJoin = join;
				}

				_tables.add(table);
			}
		}

		private JoinStepASTNodeListener(T table) {
			_table = table;
		}

		private final Set> _columnTables = Collections.newSetFromMap(
			new IdentityHashMap<>());
		private Table _fromTable;
		private boolean _hasRequiredTable;
		private Join _invalidJoin;
		private boolean _invalidJoinOrder;
		private JoinType _invalidJoinType;
		private Operand _invalidOperand;
		private final T _table;
		private final Set> _tables = Collections.newSetFromMap(
			new IdentityHashMap<>());

	}

	private static class ValidationFromStep implements FromStep {

		@Override
		public Table as(String name) {
			throw new UnsupportedOperationException();
		}

		@Override
		public Table as(
			String name, Collection> templateColumns) {

			throw new UnsupportedOperationException();
		}

		@Override
		public > T as(String name, T templateTable) {
			throw new UnsupportedOperationException();
		}

		@Override
		public JoinStep from(Table table) {
			return new From(this, table);
		}

		@Override
		public void toSQL(
			Consumer consumer, ASTNodeListener astNodeListener) {

			consumer.accept(StringPool.TRIPLE_PERIOD);
		}

		@Override
		public DSLQuery union(DSLQuery dslQuery) {
			throw new UnsupportedOperationException();
		}

		@Override
		public DSLQuery unionAll(DSLQuery dslQuery) {
			throw new UnsupportedOperationException();
		}

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy