/******************************************************************************* * Copyright (c) 2013, 2015 Oracle. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * 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 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 = (DatabaseTable)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} */ 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(); } }