blob: 7cabbd6478a07bbb924468211a66c165052acf48 [file] [log] [blame]
/*
* Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// 11/06/2013-2.5.1 Chris Delahunt
// - 374771 : TREAT support
package org.eclipse.persistence.internal.expressions;
import java.io.IOException;
import java.io.Serializable;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.history.DecoratedDatabaseTable;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.platform.database.DB2MainframePlatform;
/**
* Holder class storing a QueryKeyExpression representing an outer join
* plus some data calculated by method appendFromClauseForOuterJoin.
*/
public class OuterJoinExpressionHolder implements Comparable, Serializable
{
final ObjectExpression joinExpression;
DatabaseTable targetTable;
DatabaseTable sourceTable;
DatabaseTable targetAlias;
DatabaseTable sourceAlias;
List<DatabaseTable> additionalTargetTables;
List<DatabaseTable> additionalTargetAliases;
List<Expression> additionalJoinOnExpression;
List<Boolean> additionalTargetIsDescriptorTable;
Boolean hasInheritance;
List<Integer> indexList;
// if it's a map then an additional holder created for the key.
// mapKeyHolder is not used in sorting because there can be no outer joins out of it,
// the main reason for it to exist is its printAdditionalJoins method.
OuterJoinExpressionHolder mapKeyHolder;
// indicates whether it's a mapKeyHolder
boolean isMapKeyHolder;
Expression outerJoinedMappingCriteria;
// table join expressions keyed by the tables
Map<DatabaseTable, Expression> outerJoinedAdditionalJoinCriteria;
// used in case no corresponding outerJoinExpression is provided -
// only multi-table inheritance should be outer joined
ClassDescriptor descriptor;
SQLSelectStatement statement;
public OuterJoinExpressionHolder(SQLSelectStatement statement, ObjectExpression joinExpression, Expression outerJoinedMappingCriteria,
Map<DatabaseTable, Expression> outerJoinedAdditionalJoinCriteria, ClassDescriptor descriptor) {
this.statement = statement;
this.joinExpression = joinExpression;
this.outerJoinedMappingCriteria = outerJoinedMappingCriteria;
this.outerJoinedAdditionalJoinCriteria = outerJoinedAdditionalJoinCriteria;
this.descriptor = descriptor;
}
/*
* Used for MapKeys
*/
public OuterJoinExpressionHolder(OuterJoinExpressionHolder holder) {
this.joinExpression = holder.joinExpression;
this.outerJoinedMappingCriteria = holder.outerJoinedMappingCriteria;
this.outerJoinedAdditionalJoinCriteria = holder.outerJoinedAdditionalJoinCriteria;
this.descriptor = holder.descriptor;
}
protected void process(boolean usesHistory) {
process(usesHistory, false);
}
protected void process(boolean usesHistory, boolean isMapKeyHolder) {
this.isMapKeyHolder = isMapKeyHolder;
if (this.joinExpression instanceof QueryKeyExpression) {
QueryKeyExpression expression = (QueryKeyExpression)this.joinExpression;
if (isMapKeyHolder) {
descriptor = expression.getMapKeyDescriptor();
this.targetTable = descriptor.getTables().get(0);
this.targetAlias = outerJoinedMappingCriteria.aliasForTable(this.targetTable);
} else {
// this is a map - create a holder for the key
if(expression.isMapKeyObjectRelationship()) {
this.mapKeyHolder = new OuterJoinExpressionHolder(this);
this.mapKeyHolder.process(usesHistory, true);
}
// in DirectCollection case descriptor is null
descriptor = expression.getDescriptor();
this.targetTable = expression.getReferenceTable();
this.targetAlias = expression.aliasForTable(this.targetTable);
}
this.sourceTable = expression.getSourceTable();
this.sourceAlias = expression.getBaseExpression().aliasForTable(this.sourceTable);
} else if (this.joinExpression != null) {
this.sourceTable = ((ObjectExpression)this.joinExpression.getJoinSource()).getDescriptor().getTables().get(0);
this.sourceAlias = this.joinExpression.getJoinSource().aliasForTable(this.sourceTable);
this.targetTable = this.joinExpression.getDescriptor().getTables().get(0);
this.targetAlias = this.joinExpression.aliasForTable(this.targetTable);
} else {
// absence of join expression means that this holder used for multitable inheritance:
// ReadAllQuery query = new ReadAllQuery(Project.class);
// query.setShouldOuterJoinSubclasses(true);
// will produce:
// SELECT ... FROM PROJECT t0 LEFT OUTER JOIN LPROJECT t1 ON (t1.PROJ_ID = t0.PROJ_ID)
sourceTable = descriptor.getTables().get(0);
targetTable = descriptor.getInheritancePolicy().getChildrenTables().get(0);
Expression exp = outerJoinedAdditionalJoinCriteria.get(targetTable);
sourceAlias = exp.aliasForTable(sourceTable);
targetAlias = exp.aliasForTable(targetTable);
}
if(usesHistory) {
sourceTable = getTableAliases().get(sourceAlias);
targetTable = getTableAliases().get(targetAlias);
}
if(outerJoinedAdditionalJoinCriteria != null && !outerJoinedAdditionalJoinCriteria.isEmpty()) {
if(descriptor == null) {
descriptor = joinExpression.getDescriptor();
}
List<DatabaseTable> targetTables = descriptor.getTables();
int nDescriptorTables = targetTables.size();
hasInheritance = descriptor.hasInheritance();
if(hasInheritance) {
targetTables = descriptor.getInheritancePolicy().getAllTables();
}
int tablesSize = targetTables.size();
// skip main table - start with i=1
for(int i=1; i < tablesSize; i++) {
DatabaseTable table = targetTables.get(i);
Expression onExpression = outerJoinedAdditionalJoinCriteria.get(table);
if (onExpression != null) {
DatabaseTable alias = onExpression.aliasForTable(table);
if (usesHistory) {
table = getTableAliases().get(alias);
}
if (this.additionalTargetAliases == null) {
this.additionalTargetAliases = new ArrayList();
this.additionalTargetTables = new ArrayList();
this.additionalJoinOnExpression = new ArrayList();
this.additionalTargetIsDescriptorTable = new ArrayList();
}
this.additionalTargetAliases.add(alias);
this.additionalTargetTables.add(table);
this.additionalJoinOnExpression.add(onExpression);
// if it's the descriptor's own table - true; otherwise (it's the child's table) - false.
this.additionalTargetIsDescriptorTable.add(i < nDescriptorTables);
}
}
}
}
public boolean hasAdditionalJoinExpressions() {
return this.additionalTargetTables != null;
}
public boolean hasMapKeyHolder() {
return this.mapKeyHolder != null;
}
public void createIndexList(Map<DatabaseTable, OuterJoinExpressionHolder> targetAliasToHolders, Map<DatabaseTable, Integer> aliasToIndexes) {
if(this.indexList != null) {
// indexList has been already created
return;
}
this.indexList = new ArrayList();
OuterJoinExpressionHolder baseHolder = targetAliasToHolders.get(this.sourceAlias);
if(baseHolder != null) {
baseHolder.createIndexList(targetAliasToHolders, aliasToIndexes);
this.indexList.addAll(baseHolder.indexList);
} else {
this.indexList.add(aliasToIndexes.get(this.sourceAlias));
}
this.indexList.add(aliasToIndexes.get(this.targetAlias));
}
/*
* The method should be called only on instances of OuterJoinExpressionHolder
* and only after the indexList has been created.
* Loop through both lists comparing the members corresponding to the same index
* until not equal members are found.
* If all the members are the same, but one of the lists is shorter then it's less.
* Examples:
* {2, 1} < {2, 2}; {2, 1} < {3}; {2, 1} > {2}
*/
@Override
public int compareTo(Object other) {
if(other == this) {
return 0;
}
List<Integer> otherIndexList = ((OuterJoinExpressionHolder)other).indexList;
int nMinSize = this.indexList.size();
int nCompare = -1;
int nOtherSize = otherIndexList.size();
if(nMinSize > nOtherSize) {
nMinSize = nOtherSize;
nCompare = 1;
} else if(nMinSize == nOtherSize) {
nCompare = 0;
}
for(int i=0; i < nMinSize; i++) {
int index = indexList.get(i);
int otherIndex = otherIndexList.get(i);
if(index < otherIndex) {
return -1;
} else if(index > otherIndex) {
return 1;
}
}
return nCompare;
}
void printAdditionalJoins(ExpressionSQLPrinter printer, List<DatabaseTable> outerJoinedAliases, Collection aliasesOfTablesToBeLocked, boolean shouldPrintUpdateClauseForAllTables) throws IOException {
Writer writer = printer.getWriter();
AbstractSession session = printer.getSession();
int size = this.additionalTargetAliases.size();
for(int i=0; i < size; i++) {
DatabaseTable table = this.additionalTargetTables.get(i);
if(this.additionalTargetIsDescriptorTable.get(i)) {
// it's descriptor's own table
if (!session.getPlatform().supportsANSIInnerJoinSyntax()) {
// if the DB does not support 'JOIN', do a:
if (this.hasInheritance) {
// right outer join instead. This will give the same
// result because the right table has no rows that
// are not in the left table (left table maps to the
// main class, right table to a subclass in an
// inheritance mapping with a joined subclass
// strategy).
writer.write(" RIGHT OUTER");
} else {
// left outer join instead. This will give the same
// result because the left table has no rows that
// are not in the right table (left table is either
// a join table or it is joining secondary tables to
// a primary table).
writer.write(" LEFT OUTER");
}
}
writer.write(" JOIN ");
} else {
// it's child's table
writer.write(" LEFT OUTER JOIN ");
}
DatabaseTable alias = this.additionalTargetAliases.get(i);
table.printSQL(printer);
writer.write(" ");
if (alias.isDecorated()) {
((DecoratedDatabaseTable)alias).getAsOfClause().printSQL(printer);
writer.write(" ");
}
outerJoinedAliases.add(alias);
alias.printSQL(printer);
if (shouldPrintUpdateClauseForAllTables || (aliasesOfTablesToBeLocked != null && aliasesOfTablesToBeLocked.remove(alias))) {
getForUpdateClause().printSQL(printer, statement/*SQLSelectStatement.this*/);
}
writer.write(" ON ");
if (session.getPlatform() instanceof DB2MainframePlatform) {
((RelationExpression)this.additionalJoinOnExpression.get(i)).printSQLNoParens(printer);
} else {
this.additionalJoinOnExpression.get(i).printSQL(printer);
}
}
}
/**
* INTERNAL:
* Return the aliases used.
*/
public Map<DatabaseTable, DatabaseTable> getTableAliases() {
return statement.getTableAliases();
}
protected ForUpdateClause getForUpdateClause() {
return statement.getForUpdateClause();
}
}