| /* |
| * Copyright (c) 1998, 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: |
| // Oracle - initial API and implementation from Oracle TopLink |
| // 20/11/2012-2.5 Guy Pelletier |
| // - 394524: Invalid query key [...] in expression |
| // 04/30/2014-2.6 Lukas Jungmann |
| // - 380101: Invalid MySQL SQL syntax in query with LIMIT and FOR UPDATE |
| // IBM - Bug 537795: CASE THEN and ELSE scalar expression Constants should not be casted to CASE operand type |
| package org.eclipse.persistence.internal.expressions; |
| |
| import static org.eclipse.persistence.queries.ReadAllQuery.Direction.CHILD_TO_PARENT; |
| |
| import java.io.CharArrayWriter; |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| import java.util.Vector; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.exceptions.QueryException; |
| import org.eclipse.persistence.exceptions.ValidationException; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.expressions.ExpressionBuilder; |
| import org.eclipse.persistence.expressions.ExpressionOperator; |
| import org.eclipse.persistence.history.AsOfClause; |
| import org.eclipse.persistence.internal.databaseaccess.DatabaseCall; |
| import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform; |
| import org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.helper.DatabaseTable; |
| import org.eclipse.persistence.internal.helper.FunctionField; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.helper.NonSynchronizedVector; |
| import org.eclipse.persistence.internal.history.DecoratedDatabaseTable; |
| import org.eclipse.persistence.internal.history.UniversalAsOfClause; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.mappings.AggregateCollectionMapping; |
| import org.eclipse.persistence.mappings.AggregateObjectMapping; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.mappings.DirectCollectionMapping; |
| import org.eclipse.persistence.mappings.ManyToManyMapping; |
| import org.eclipse.persistence.mappings.ObjectReferenceMapping; |
| import org.eclipse.persistence.mappings.OneToManyMapping; |
| import org.eclipse.persistence.mappings.OneToOneMapping; |
| import org.eclipse.persistence.queries.DatabaseQuery; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.queries.ReadAllQuery; |
| import org.eclipse.persistence.queries.ReadQuery; |
| import org.eclipse.persistence.queries.SQLCall; |
| |
| /** |
| * <p><b>Purpose</b>: Print SELECT statement. |
| * <p><b>Responsibilities</b>:<ul> |
| * <li> Print SELECT statement. |
| * </ul> |
| * @author Dorin Sandu |
| * @since TOPLink/Java 1.0 |
| */ |
| public class SQLSelectStatement extends SQLStatement { |
| |
| /** Query this statement is associated to (used for SQL query options). */ |
| protected ReadQuery query; |
| /** Flag used to indicate field names should use unique aliases */ |
| protected boolean useUniqueFieldAliases; |
| /** Counter to generate unique alias names */ |
| protected int fieldCounter=0; |
| |
| /** Fields being selected (can include expressions). */ |
| protected Vector fields; |
| |
| /** Fields not being selected (can include expressions). */ |
| protected List<Object> nonSelectFields; |
| |
| /** Tables being selected from. */ |
| protected List<DatabaseTable> tables; |
| |
| /** Used for "Select Distinct" option. */ |
| protected short distinctState; |
| |
| /** Order by clause for read all queries. */ |
| protected List<Expression> orderByExpressions; |
| |
| /** Group by clause for report queries. */ |
| protected List<Expression> groupByExpressions; |
| |
| /** Union clause. */ |
| protected List<Expression> unionExpressions; |
| |
| /** Having clause for report queries. */ |
| protected Expression havingExpression; |
| |
| /** Used for pessimistic locking ie. "For Update". */ |
| protected ForUpdateClause forUpdateClause; |
| |
| /** Used for report query or counts so we know how to treat distincts. */ |
| protected boolean isAggregateSelect; |
| |
| /** Used for DB2 style from clause outer joins. */ |
| protected List<OuterJoinExpressionHolder> outerJoinExpressionHolders; |
| |
| /** Used for Oracle Hierarchical Queries */ |
| protected Expression startWithExpression; |
| protected Expression connectByExpression; |
| protected List<Expression> orderSiblingsByExpressions; |
| protected ReadAllQuery.Direction direction; |
| |
| /** Variables used for aliasing and normalizing. */ |
| protected boolean requiresAliases; |
| protected Map<DatabaseTable, DatabaseTable> tableAliases; |
| protected DatabaseTable lastTable; |
| protected DatabaseTable currentAlias; |
| protected int currentAliasNumber; |
| |
| /** Used for subselects. */ |
| protected SQLSelectStatement parentStatement; |
| |
| /** It is used by subselect to re-normalize joins */ |
| protected Map<Expression, Expression> optimizedClonedExpressions; |
| |
| /** Used for caching the field alias written to the query */ |
| protected Map<DatabaseField, String> fieldAliases; |
| protected boolean shouldCacheFieldAliases; |
| |
| public SQLSelectStatement() { |
| this.fields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(2); |
| this.tables = new ArrayList(4); |
| this.requiresAliases = false; |
| this.useUniqueFieldAliases=false; |
| this.isAggregateSelect = false; |
| this.distinctState = ObjectLevelReadQuery.UNCOMPUTED_DISTINCT; |
| this.currentAliasNumber = 0; |
| } |
| |
| public void addField(DatabaseField field) { |
| getFields().addElement(field); |
| } |
| |
| /** |
| * INTERNAL: adds an expression to the fields. set a flag if the expression |
| * is for and aggregate function. |
| */ |
| public void addField(Expression expression) { |
| if (expression instanceof FunctionExpression) { |
| if (expression.getOperator().isAggregateOperator()) { |
| setIsAggregateSelect(true); |
| } |
| } |
| |
| getFields().add(expression); |
| } |
| |
| /** |
| * When distinct is used with order by the ordered fields must be in the select clause. |
| */ |
| protected void addOrderByExpressionToSelectForDistinct() { |
| for (Expression orderExpression : getOrderByExpressions()) { |
| Expression fieldExpression = orderExpression; |
| |
| while (fieldExpression.isFunctionExpression() && (fieldExpression.getOperator().isOrderOperator())) { |
| fieldExpression = ((FunctionExpression)fieldExpression).getBaseExpression(); |
| } |
| |
| // Changed to call a method to loop through the fields vector and check each element |
| // individually. Jon D. May 4, 2000 for pr 7811 |
| if ((fieldExpression.selectIfOrderedBy()) && !fieldsContainField(getFields(), fieldExpression)) { |
| addField(fieldExpression); |
| } |
| } |
| } |
| |
| /** |
| * Add a table to the statement. The table will |
| * be used in the FROM part of the SQL statement. |
| */ |
| public void addTable(DatabaseTable table) { |
| if (!getTables().contains(table)) { |
| getTables().add(table); |
| } |
| } |
| |
| /** |
| * ADVANCED: |
| * If a platform is Informix, then the outer join must be in the FROM clause. |
| * This is used internally by EclipseLink for building Informix outer join syntax which differs from |
| * other platforms (Oracle,Sybase) that print the outer join in the WHERE clause and from DB2 which prints |
| * the OuterJoinedAliases passed in to keep track of tables used for outer join so no normal join is given. |
| * This syntax is old for Informix, so should probably be removed. |
| */ |
| public void appendFromClauseForInformixOuterJoin(ExpressionSQLPrinter printer, List<DatabaseTable> outerJoinedAliases) throws IOException { |
| Writer writer = printer.getWriter(); |
| |
| // Print outer joins |
| boolean firstTable = true; |
| |
| for (OuterJoinExpressionHolder holder : getOuterJoinExpressionsHolders()) { |
| QueryKeyExpression outerExpression = (QueryKeyExpression)holder.joinExpression; |
| CompoundExpression relationExpression = (CompoundExpression)holder.outerJoinedMappingCriteria;// get expression for multiple table case |
| |
| // CR#3083929 direct collection/map mappings do not have reference descriptor. |
| DatabaseTable targetTable = null; |
| if (outerExpression.getMapping().isDirectCollectionMapping()) { |
| targetTable = ((DirectCollectionMapping)outerExpression.getMapping()).getReferenceTable(); |
| } else { |
| targetTable = outerExpression.getMapping().getReferenceDescriptor().getTables().get(0); |
| } |
| // Grab the source table from the mapping not just the first table |
| // from the descriptor. In an joined inheritance hierarchy, the |
| // fk used in the outer join may be from a subclasses's table . |
| DatabaseTable sourceTable; |
| if (outerExpression.getMapping().isObjectReferenceMapping() && ((ObjectReferenceMapping) outerExpression.getMapping()).isForeignKeyRelationship()) { |
| sourceTable = (outerExpression.getMapping().getFields().get(0)).getTable(); |
| } else { |
| sourceTable = ((ObjectExpression)outerExpression.getBaseExpression()).getDescriptor().getTables().get(0); |
| } |
| |
| DatabaseTable sourceAlias = outerExpression.getBaseExpression().aliasForTable(sourceTable); |
| DatabaseTable targetAlias = outerExpression.aliasForTable(targetTable); |
| |
| if (!(outerJoinedAliases.contains(sourceAlias) || outerJoinedAliases.contains(targetAlias))) { |
| if (!firstTable) { |
| writer.write(", "); |
| } |
| |
| firstTable = false; |
| writer.write(sourceTable.getQualifiedNameDelimited(printer.getPlatform())); |
| outerJoinedAliases.add(sourceAlias); |
| writer.write(" "); |
| writer.write(sourceAlias.getQualifiedNameDelimited(printer.getPlatform())); |
| |
| if (outerExpression.getMapping().isManyToManyMapping()) {// for many to many mappings, you need to do some funky stuff to get the relation table's alias |
| DatabaseTable newTarget = ((ManyToManyMapping)outerExpression.getMapping()).getRelationTable(); |
| DatabaseTable newAlias = relationExpression.aliasForTable(newTarget); |
| writer.write(", OUTER ");// need to outer join only to relation table for many-to-many case in Informix |
| writer.write(newTarget.getQualifiedNameDelimited(printer.getPlatform())); |
| writer.write(" "); |
| outerJoinedAliases.add(newAlias); |
| writer.write(newAlias.getQualifiedNameDelimited(printer.getPlatform())); |
| } else if (outerExpression.getMapping().isDirectCollectionMapping()) {// for many to many mappings, you need to do some funky stuff to get the relation table's alias |
| DatabaseTable newTarget = ((DirectCollectionMapping)outerExpression.getMapping()).getReferenceTable(); |
| DatabaseTable newAlias = relationExpression.aliasForTable(newTarget); |
| writer.write(", OUTER "); |
| writer.write(newTarget.getQualifiedNameDelimited(printer.getPlatform())); |
| writer.write(" "); |
| outerJoinedAliases.add(newAlias); |
| writer.write(newAlias.getQualifiedNameDelimited(printer.getPlatform())); |
| } else {// do normal outer stuff for Informix |
| for (Enumeration<DatabaseTable> target = outerExpression.getMapping().getReferenceDescriptor().getTables().elements(); |
| target.hasMoreElements();) { |
| DatabaseTable newTarget = target.nextElement(); |
| DatabaseTable newAlias = outerExpression.aliasForTable(newTarget); |
| writer.write(", OUTER "); |
| writer.write(newTarget.getQualifiedNameDelimited(printer.getPlatform())); |
| writer.write(" "); |
| outerJoinedAliases.add(newAlias); |
| writer.write(newAlias.getQualifiedNameDelimited(printer.getPlatform())); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * ADVANCED: |
| * Appends the SQL standard outer join clause, and some variation per platform. |
| * Most platforms use this syntax, support is also offered for Oracle to join in the where clause (although it should use the FROM clause as the WHERE clause is obsolete). |
| * This is also used for inner joins when configured in the platform. |
| */ |
| public void appendFromClauseForOuterJoin(ExpressionSQLPrinter printer, List<DatabaseTable> outerJoinedAliases, Collection aliasesOfTablesToBeLocked, boolean shouldPrintUpdateClauseForAllTables) throws IOException { |
| Writer writer = printer.getWriter(); |
| AbstractSession session = printer.getSession(); |
| DatabasePlatform platform = session.getPlatform(); |
| |
| // Print outer joins |
| boolean firstTable = true; |
| boolean requiresEscape = false; // Checks if the JDBC closing escape syntax is needed. |
| |
| boolean usesHistory = (getBuilder() != null) && getBuilder().hasAsOfClause(); |
| |
| int nSize = getOuterJoinExpressionsHolders().size(); |
| for (OuterJoinExpressionHolder holder : getOuterJoinExpressionsHolders()) { |
| holder.process(usesHistory); |
| } |
| |
| if(nSize > 1) { |
| sortOuterJoinExpressionHolders(getOuterJoinExpressionsHolders()); |
| } |
| |
| for (OuterJoinExpressionHolder holder : outerJoinExpressionHolders) { |
| ObjectExpression outerExpression = holder.joinExpression; |
| boolean isOuterJoin = (outerExpression == null) || outerExpression.shouldUseOuterJoin(); |
| DatabaseTable targetTable = holder.targetTable; |
| DatabaseTable sourceTable = holder.sourceTable; |
| DatabaseTable sourceAlias = holder.sourceAlias; |
| DatabaseTable targetAlias = holder.targetAlias; |
| |
| if (!outerJoinedAliases.contains(targetAlias)) { |
| if (!outerJoinedAliases.contains(sourceAlias)) { |
| if (requiresEscape && session.getPlatform().shouldUseJDBCOuterJoinSyntax()) { |
| writer.write("}"); |
| } |
| if (!firstTable) { |
| writer.write(","); |
| } |
| if (platform.shouldUseJDBCOuterJoinSyntax()) { |
| writer.write(platform.getJDBCOuterJoinString()); |
| } |
| requiresEscape = true; |
| firstTable = false; |
| sourceTable.printSQL(printer); |
| outerJoinedAliases.add(sourceAlias); |
| writer.write(" "); |
| if (sourceAlias.isDecorated()) { |
| ((DecoratedDatabaseTable)sourceAlias).getAsOfClause().printSQL(printer); |
| writer.write(" "); |
| } |
| sourceAlias.printSQL(printer); |
| printForUpdateClauseOnJoin(sourceAlias, printer, shouldPrintUpdateClauseForAllTables, aliasesOfTablesToBeLocked, platform); |
| } |
| |
| if (outerExpression == null) { |
| holder.printAdditionalJoins(printer, outerJoinedAliases, aliasesOfTablesToBeLocked, shouldPrintUpdateClauseForAllTables); |
| } else { |
| DatabaseTable relationTable = outerExpression.getRelationTable(); |
| boolean hasAdditionalJoinExpressions = holder.hasAdditionalJoinExpressions(); |
| boolean isMapKeyObject = holder.hasMapKeyHolder(); |
| Expression additionalOnExpression = outerExpression.getOnClause(); |
| if (relationTable == null) { |
| if (outerExpression.isDirectCollection()) { |
| // Append the join clause, |
| // If this is a direct collection, join to direct table. |
| Expression onExpression = holder.outerJoinedMappingCriteria; |
| |
| DatabaseTable newAlias = onExpression.aliasForTable(targetTable); |
| if (isOuterJoin) { |
| writer.write(" LEFT OUTER JOIN "); |
| } else { |
| writer.write(" JOIN "); |
| } |
| targetTable.printSQL(printer); |
| writer.write(" "); |
| if (newAlias.isDecorated()) { |
| ((DecoratedDatabaseTable)newAlias).getAsOfClause().printSQL(printer); |
| writer.write(" "); |
| } |
| outerJoinedAliases.add(newAlias); |
| newAlias.printSQL(printer); |
| printForUpdateClauseOnJoin(newAlias, printer, shouldPrintUpdateClauseForAllTables, aliasesOfTablesToBeLocked, platform); |
| printOnClause(onExpression.and(additionalOnExpression), printer, platform); |
| } else { |
| // Must outerjoin each of the targets tables. |
| // The first table is joined with the mapping join criteria, |
| // the rest of the tables are joined with the additional join criteria. |
| if (isOuterJoin) { |
| writer.write(" LEFT OUTER JOIN "); |
| } else { |
| writer.write(" JOIN "); |
| } |
| if (hasAdditionalJoinExpressions && platform.supportsNestingOuterJoins()) { |
| writer.write("("); |
| } |
| targetTable.printSQL(printer); |
| writer.write(" "); |
| if (targetAlias.isDecorated()) { |
| ((DecoratedDatabaseTable)targetAlias).getAsOfClause().printSQL(printer); |
| writer.write(" "); |
| } |
| outerJoinedAliases.add(targetAlias); |
| targetAlias.printSQL(printer); |
| printForUpdateClauseOnJoin(targetAlias, printer, shouldPrintUpdateClauseForAllTables, aliasesOfTablesToBeLocked, platform); |
| if (hasAdditionalJoinExpressions && platform.supportsNestingOuterJoins()) { |
| holder.printAdditionalJoins(printer, outerJoinedAliases, aliasesOfTablesToBeLocked, shouldPrintUpdateClauseForAllTables); |
| writer.write(")"); |
| } |
| Expression sourceToTargetJoin = holder.outerJoinedMappingCriteria; |
| if (additionalOnExpression != null) { |
| if (sourceToTargetJoin == null) { |
| sourceToTargetJoin = additionalOnExpression; |
| } else { |
| sourceToTargetJoin = sourceToTargetJoin.and(additionalOnExpression); |
| } |
| } |
| printOnClause(sourceToTargetJoin, printer, platform); |
| if (hasAdditionalJoinExpressions && !platform.supportsNestingOuterJoins()) { |
| holder.printAdditionalJoins(printer, outerJoinedAliases, aliasesOfTablesToBeLocked, shouldPrintUpdateClauseForAllTables); |
| } |
| } |
| } else { |
| // Bug#4240751 Treat ManyToManyMapping separately for out join |
| // Must outer join each of the targets tables. |
| // The first table is joined with the mapping join criteria, |
| // the rest of the tables are joined with the additional join criteria. |
| // For example: EMPLOYEE t1 LEFT OUTER JOIN (PROJ_EMP t3 LEFT OUTER JOIN PROJECT t0 ON (t0.PROJ_ID = t3.PROJ_ID)) ON (t3.EMP_ID = t1.EMP_ID) |
| // Now OneToOneMapping also may have relation table. |
| DatabaseTable relationAlias = holder.outerJoinedMappingCriteria.aliasForTable(relationTable); |
| DatabaseTable mapKeyAlias = null; |
| DatabaseTable mapKeyTable = null; |
| List<DatabaseTable> tablesInOrder = new ArrayList(); |
| // glassfish issue 2440: store aliases instead of tables |
| // in the tablesInOrder. This allows to distinguish source |
| // and target table in case of an self referencing relationship. |
| tablesInOrder.add(sourceAlias); |
| tablesInOrder.add(relationAlias); |
| tablesInOrder.add(targetAlias); |
| if (isMapKeyObject) { |
| // Need to also join the map key key. |
| mapKeyAlias = holder.mapKeyHolder.targetAlias; |
| mapKeyTable = holder.mapKeyHolder.targetTable; |
| tablesInOrder.add(mapKeyAlias); |
| } |
| TreeMap indexToExpressionMap = new TreeMap(); |
| mapTableIndexToExpression(holder.outerJoinedMappingCriteria, indexToExpressionMap, tablesInOrder); |
| Expression sourceToRelationJoin = (Expression)indexToExpressionMap.get(1); |
| Expression relationToTargetJoin = (Expression)indexToExpressionMap.get(2); |
| Expression relationToKeyJoin = null; |
| if (isMapKeyObject) { |
| relationToKeyJoin = (Expression)indexToExpressionMap.get(3); |
| } |
| |
| if (outerExpression.shouldUseOuterJoin()) { |
| writer.write(" LEFT OUTER JOIN "); |
| } else { |
| writer.write(" JOIN "); |
| } |
| if (platform.supportsNestingOuterJoins()) { |
| writer.write("("); |
| } |
| relationTable.printSQL(printer); |
| writer.write(" "); |
| if (relationAlias.isDecorated()) { |
| ((DecoratedDatabaseTable)relationAlias).getAsOfClause().printSQL(printer); |
| writer.write(" "); |
| } |
| outerJoinedAliases.add(relationAlias); |
| relationAlias.printSQL(printer); |
| printForUpdateClauseOnJoin(relationAlias, printer, shouldPrintUpdateClauseForAllTables, aliasesOfTablesToBeLocked, platform); |
| if (!platform.supportsNestingOuterJoins()) { |
| printOnClause(sourceToRelationJoin.and(additionalOnExpression), printer, platform); |
| } |
| |
| if (isMapKeyObject) { |
| // Append join to map key. |
| if (isOuterJoin && !session.getPlatform().supportsANSIInnerJoinSyntax()) { |
| writer.write(" LEFT OUTER"); |
| } |
| writer.write(" JOIN "); |
| mapKeyTable.printSQL(printer); |
| writer.write(" "); |
| if (mapKeyAlias.isDecorated()) { |
| ((DecoratedDatabaseTable)mapKeyAlias).getAsOfClause().printSQL(printer); |
| writer.write(" "); |
| } |
| outerJoinedAliases.add(mapKeyAlias); |
| mapKeyAlias.printSQL(printer); |
| printForUpdateClauseOnJoin(mapKeyAlias, printer, shouldPrintUpdateClauseForAllTables, aliasesOfTablesToBeLocked, platform); |
| printOnClause(relationToKeyJoin.and(additionalOnExpression), printer, platform); |
| |
| if (holder.mapKeyHolder.hasAdditionalJoinExpressions()) { |
| holder.mapKeyHolder.printAdditionalJoins(printer, outerJoinedAliases, aliasesOfTablesToBeLocked, shouldPrintUpdateClauseForAllTables); |
| } |
| } |
| |
| if (isOuterJoin && !session.getPlatform().supportsANSIInnerJoinSyntax()) { |
| // if the DB does not support 'JOIN', do a left outer |
| // join instead. This will give the same result because |
| // the left table is a join table and has therefore |
| // no rows that are not in the right table. |
| writer.write(" LEFT OUTER"); |
| } |
| writer.write(" JOIN "); |
| targetTable.printSQL(printer); |
| writer.write(" "); |
| if (targetAlias.isDecorated()) { |
| ((DecoratedDatabaseTable)targetAlias).getAsOfClause().printSQL(printer); |
| writer.write(" "); |
| } |
| outerJoinedAliases.add(targetAlias); |
| targetAlias.printSQL(printer); |
| printForUpdateClauseOnJoin(targetAlias, printer, shouldPrintUpdateClauseForAllTables, aliasesOfTablesToBeLocked, platform); |
| printOnClause(relationToTargetJoin, printer, platform); |
| |
| if (hasAdditionalJoinExpressions) { |
| holder.printAdditionalJoins(printer, outerJoinedAliases, aliasesOfTablesToBeLocked, shouldPrintUpdateClauseForAllTables); |
| } |
| if (platform.supportsNestingOuterJoins()) { |
| writer.write(")"); |
| printOnClause(sourceToRelationJoin, printer, platform); |
| } |
| } |
| } |
| } |
| } |
| |
| if (requiresEscape && session.getPlatform().shouldUseJDBCOuterJoinSyntax()) { |
| writer.write("}"); |
| } |
| } |
| |
| /** |
| * Print the outer join ON clause. |
| * Some databases do not allow brackets. |
| */ |
| protected void printOnClause(Expression onClause, ExpressionSQLPrinter printer, DatabasePlatform platform) throws IOException { |
| printer.getWriter().write(" ON "); |
| if (!platform.supportsOuterJoinsWithBrackets()) { |
| ((RelationExpression)onClause).printSQLNoParens(printer); |
| } else { |
| onClause.printSQL(printer); |
| } |
| } |
| |
| /** |
| * Print the FOR UPDATE clause after each join if required. |
| */ |
| protected void printForUpdateClauseOnJoin(DatabaseTable alias, ExpressionSQLPrinter printer, boolean shouldPrintUpdateClauseForAllTables, Collection aliasesOfTablesToBeLocked, DatabasePlatform platform) { |
| if (shouldPrintUpdateClauseForAllTables || (aliasesOfTablesToBeLocked != null && aliasesOfTablesToBeLocked.remove(alias))) { |
| getForUpdateClause().printSQL(printer, this); |
| } |
| } |
| |
| /** |
| * Print the from clause. |
| * This includes outer joins, these must be printed before the normal join to ensure that the source tables are not joined again. |
| * Outer joins are not printed in the FROM clause on Oracle or Sybase. |
| */ |
| public void appendFromClauseToWriter(ExpressionSQLPrinter printer) throws IOException { |
| Writer writer = printer.getWriter(); |
| AbstractSession session = printer.getSession(); |
| writer.write(" FROM "); |
| |
| // Print outer joins |
| boolean firstTable = true; |
| List<DatabaseTable> outerJoinedAliases = new ArrayList(4); // Must keep track of tables used for outer join so no normal join is given |
| |
| // prepare to lock tables if required |
| boolean shouldPrintUpdateClause = printer.getPlatform().shouldPrintForUpdateClause() |
| && !printer.getPlatform().shouldPrintLockingClauseAfterWhereClause() |
| && (getForUpdateClause() != null); |
| Collection aliasesOfTablesToBeLocked = null; |
| boolean shouldPrintUpdateClauseForAllTables = false; |
| if (shouldPrintUpdateClause) { |
| aliasesOfTablesToBeLocked = getForUpdateClause().getAliasesOfTablesToBeLocked(this); |
| shouldPrintUpdateClauseForAllTables = aliasesOfTablesToBeLocked.size() == getTableAliases().size(); |
| } |
| |
| if (hasOuterJoinExpressions()) { |
| if (session.getPlatform().isInformixOuterJoin()) { |
| appendFromClauseForInformixOuterJoin(printer, outerJoinedAliases); |
| } else if (!session.getPlatform().shouldPrintOuterJoinInWhereClause() || !session.getPlatform().shouldPrintInnerJoinInWhereClause()) { |
| appendFromClauseForOuterJoin(printer, outerJoinedAliases, aliasesOfTablesToBeLocked, shouldPrintUpdateClauseForAllTables); |
| } |
| firstTable = false; |
| } |
| |
| // If there are no table aliases it means the query was malformed, |
| // most likely the wrong builder was used, or wrong builder on the left in a sub-query. |
| if (getTableAliases().isEmpty()) { |
| throw QueryException.invalidBuilderInQuery(null);// Query is set in execute. |
| } |
| |
| // Print tables for normal join |
| for (DatabaseTable alias : getTableAliases().keySet()) { |
| if (!outerJoinedAliases.contains(alias)) { |
| DatabaseTable table = getTableAliases().get(alias); |
| if (requiresAliases()) { |
| if (!firstTable) { |
| writer.write(", "); |
| } |
| firstTable = false; |
| table.printSQL(printer); |
| writer.write(" "); |
| if (alias.isDecorated()) { |
| ((DecoratedDatabaseTable)alias).getAsOfClause().printSQL(printer); |
| writer.write(" "); |
| } |
| alias.printSQL(printer); |
| } else { |
| table.printSQL(printer); |
| |
| if (alias.isDecorated()) { |
| writer.write(" "); |
| ((DecoratedDatabaseTable)alias).getAsOfClause().printSQL(printer); |
| } |
| } |
| if (shouldPrintUpdateClause) { |
| if (shouldPrintUpdateClauseForAllTables || aliasesOfTablesToBeLocked.remove(alias)) { |
| getForUpdateClause().printSQL(printer, this); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * This method will append the group by clause to the end of the |
| * select statement. |
| */ |
| public void appendGroupByClauseToWriter(ExpressionSQLPrinter printer) throws IOException { |
| if (getGroupByExpressions().isEmpty()) { |
| return; |
| } |
| |
| printer.getWriter().write(" GROUP BY "); |
| |
| Vector newFields = new Vector(); |
| // to avoid printing a comma before the first field |
| printer.setIsFirstElementPrinted(false); |
| for (Expression expression : getGroupByExpressions()) { |
| // if (expression.isObjectExpression() && ((ObjectExpression)expression).getDescriptor() != null){ |
| //in the case where the user is grouping by an entity we need to change this to the PKs |
| // for (String field : ((ObjectExpression)expression).getDescriptor().getPrimaryKeyFieldNames()){ |
| // writeFieldsFromExpression(printer, expression.getField(field), newFields); |
| // } |
| // }else{ |
| writeFieldsFromExpression(printer, expression, newFields); |
| // } |
| } |
| } |
| |
| /** |
| * This method will append the Hierarchical Query Clause to the end of the |
| * select statement |
| */ |
| public void appendHierarchicalQueryClauseToWriter(ExpressionSQLPrinter printer) throws IOException { |
| Expression startWith = getStartWithExpression(); |
| Expression connectBy = getConnectByExpression(); |
| List<Expression> orderSiblingsBy = getOrderSiblingsByExpressions(); |
| |
| //Create the START WITH CLAUSE |
| if (startWith != null) { |
| printer.getWriter().write(" START WITH "); |
| startWith.printSQL(printer); |
| } |
| |
| if (connectBy != null) { |
| if (!connectBy.isQueryKeyExpression()) { |
| throw QueryException.illFormedExpression(connectBy); |
| } |
| |
| printer.getWriter().write(" CONNECT BY "); |
| |
| DatabaseMapping mapping = ((QueryKeyExpression)connectBy).getMapping(); |
| ClassDescriptor descriptor = mapping.getDescriptor(); |
| |
| //only works for these kinds of mappings. The data isn't hierarchical otherwise |
| //Should also check that the source class and target class are the same. |
| Map<DatabaseField, DatabaseField> foreignKeys = null; |
| |
| if (mapping.isOneToManyMapping()) { |
| OneToManyMapping otm = (OneToManyMapping)mapping; |
| foreignKeys = otm.getTargetForeignKeysToSourceKeys(); |
| } else if (mapping.isOneToOneMapping()) { |
| OneToOneMapping oto = (OneToOneMapping)mapping; |
| foreignKeys = oto.getSourceToTargetKeyFields(); |
| } else if (mapping.isAggregateCollectionMapping()) { |
| AggregateCollectionMapping acm = (AggregateCollectionMapping)mapping; |
| foreignKeys = acm.getTargetForeignKeyToSourceKeys(); |
| } else { |
| throw QueryException.invalidQueryKeyInExpression(connectBy); |
| } |
| |
| DatabaseTable defaultTable = descriptor.getDefaultTable(); |
| String tableName = ""; |
| |
| //determine which table name to use |
| if (requiresAliases()) { |
| tableName = getBuilder().aliasForTable(defaultTable).getName(); |
| } else { |
| tableName = defaultTable.getNameDelimited(printer.getPlatform()); |
| } |
| |
| if ((foreignKeys != null) && !foreignKeys.isEmpty()) { |
| //get the source and target fields. |
| Iterator<DatabaseField> sourceKeys = foreignKeys.keySet().iterator(); |
| |
| //for each source field, get the target field and create the link. If there's |
| //only one, use the simplest version without ugly bracets |
| if (foreignKeys.size() > 1) { |
| printer.getWriter().write("(("); |
| } |
| |
| DatabaseField source = sourceKeys.next(); |
| DatabaseField target = foreignKeys.get(source); |
| |
| ReadAllQuery.Direction direction = getDirection() != null ? getDirection() : ReadAllQuery.Direction.getDefault(mapping); |
| if (direction == CHILD_TO_PARENT) { |
| printer.getWriter().write("PRIOR " + tableName + "." + source.getNameDelimited(printer.getPlatform())); |
| printer.getWriter().write(" = " + tableName + "." + target.getNameDelimited(printer.getPlatform())); |
| } else { |
| printer.getWriter().write(tableName + "." + source.getNameDelimited(printer.getPlatform())); |
| printer.getWriter().write(" = PRIOR " + tableName + "." + target.getNameDelimited(printer.getPlatform())); |
| } |
| |
| while (sourceKeys.hasNext()) { |
| printer.getWriter().write(") AND ("); |
| source = sourceKeys.next(); |
| target = foreignKeys.get(source); |
| |
| if (direction == CHILD_TO_PARENT) { |
| printer.getWriter().write("PRIOR " + tableName + "." + source.getNameDelimited(printer.getPlatform())); |
| printer.getWriter().write(" = " + tableName + "." + target.getNameDelimited(printer.getPlatform())); |
| } else { |
| printer.getWriter().write(tableName + "." + source.getNameDelimited(printer.getPlatform())); |
| printer.getWriter().write(" = PRIOR " + tableName + "." + target.getNameDelimited(printer.getPlatform())); |
| } |
| } |
| |
| if (foreignKeys.size() > 1) { |
| printer.getWriter().write("))"); |
| } |
| } |
| } |
| |
| if ((orderSiblingsBy != null) && !orderSiblingsBy.isEmpty()) { |
| printer.getWriter().write(" ORDER SIBLINGS BY "); |
| |
| for (Iterator<Expression> iterator = orderSiblingsBy.iterator(); iterator.hasNext(); ) { |
| Expression expression = iterator.next(); |
| expression.printSQL(printer); |
| |
| if (iterator.hasNext()) { |
| printer.getWriter().write(", "); |
| } |
| } |
| } |
| } |
| |
| /** |
| * This method will append the order clause to the end of the |
| * select statement. |
| */ |
| public void appendOrderClauseToWriter(ExpressionSQLPrinter printer) throws IOException { |
| if (!hasOrderByExpressions()) { |
| return; |
| } |
| |
| printer.getWriter().write(" ORDER BY "); |
| |
| for (Iterator<Expression> expressionsEnum = getOrderByExpressions().iterator(); expressionsEnum.hasNext();) { |
| Expression expression = expressionsEnum.next(); |
| expression.printSQL(printer); |
| |
| if (expressionsEnum.hasNext()) { |
| printer.getWriter().write(", "); |
| } |
| } |
| } |
| |
| /** |
| * This method will append the union clause to the end of the |
| * select statement. |
| */ |
| public void appendUnionClauseToWriter(ExpressionSQLPrinter printer) throws IOException { |
| if (!hasUnionExpressions()) { |
| return; |
| } |
| |
| for (Iterator<Expression> expressionsEnum = getUnionExpressions().iterator(); expressionsEnum.hasNext();) { |
| Expression expression = expressionsEnum.next(); |
| printer.getWriter().write(" "); |
| expression.printSQL(printer); |
| printer.printString(")"); |
| } |
| } |
| |
| /** |
| * This method will append the for update clause to the end of the |
| * select statement. |
| */ |
| public void appendForUpdateClause(ExpressionSQLPrinter printer) { |
| if (getForUpdateClause() != null) { |
| getForUpdateClause().printSQL(printer, this); |
| } |
| } |
| |
| /** |
| * INTERNAL: Alias the tables in all of our nodes. |
| */ |
| public void assignAliases(Vector allExpressions) { |
| // For sub-selects all statements must share aliasing information. |
| // For CR#2627019 |
| currentAliasNumber = getCurrentAliasNumber(); |
| |
| ExpressionIterator iterator = new ExpressionIterator() { |
| @Override |
| public void iterate(Expression each) { |
| currentAliasNumber = each.assignTableAliasesStartingAt(currentAliasNumber); |
| } |
| }; |
| |
| if (allExpressions.isEmpty()) { |
| // bug 3878553 - ensure aliases are always assigned for when required . |
| if ((getBuilder() != null) && requiresAliases()) { |
| getBuilder().assignTableAliasesStartingAt(currentAliasNumber); |
| } |
| } else { |
| for (Enumeration expressionEnum = allExpressions.elements(); |
| expressionEnum.hasMoreElements();) { |
| Expression expression = (Expression)expressionEnum.nextElement(); |
| iterator.iterateOn(expression); |
| } |
| } |
| |
| // For sub-selects update aliasing information of all statements. |
| // For CR#2627019 |
| setCurrentAliasNumber(currentAliasNumber); |
| } |
| |
| /** |
| * Build the call, setting the query first, this is required in some cases when the query info is required to print the SQL. |
| */ |
| public DatabaseCall buildCall(AbstractSession session, DatabaseQuery query) { |
| SQLCall call = new SQLCall(); |
| call.setQuery(query); |
| call.returnManyRows(); |
| |
| Writer writer = new CharArrayWriter(200); |
| |
| ExpressionSQLPrinter printer = new ExpressionSQLPrinter(session, getTranslationRow(), call, requiresAliases(), getBuilder()); |
| printer.setWriter(writer); |
| |
| session.getPlatform().printSQLSelectStatement(call, printer, this); |
| call.setSQLString(writer.toString()); |
| |
| return call; |
| } |
| |
| /** |
| * Print the SQL representation of the statement on a stream. |
| */ |
| @Override |
| public DatabaseCall buildCall(AbstractSession session) { |
| return buildCall(session, null); |
| } |
| |
| /** |
| * INTERNAL: |
| * This is used by cursored stream to determine if an expression used distinct as the size must account for this. |
| */ |
| public void computeDistinct() { |
| ExpressionIterator iterator = new ExpressionIterator() { |
| @Override |
| public void iterate(Expression expression) { |
| if (expression.isQueryKeyExpression() && ((QueryKeyExpression)expression).shouldQueryToManyRelationship()) { |
| // Aggregate should only use distinct as specified by the user. |
| if (!isDistinctComputed()) { |
| useDistinct(); |
| } |
| } |
| } |
| }; |
| |
| if (getWhereClause() != null) { |
| iterator.iterateOn(getWhereClause()); |
| } |
| } |
| |
| public boolean isSubSelect() { |
| return (getParentStatement() != null); |
| } |
| |
| /** |
| * INTERNAL: |
| * It is used by subqueries to avoid duplicate joins. |
| */ |
| public Map<Expression, Expression> getOptimizedClonedExpressions() { |
| // Lazily Initialized only to be used by subqueries. |
| if (optimizedClonedExpressions == null) { |
| optimizedClonedExpressions = new IdentityHashMap<>(); |
| } |
| |
| return optimizedClonedExpressions; |
| } |
| |
| /** |
| * INTERNAL: |
| * It is used by subqueries to avoid duplicate joins. |
| */ |
| public void addOptimizedClonedExpressions(Expression originalKey, Expression optimizedValue) { |
| // Lazily Initialized only to be used by subqueries. |
| if (optimizedClonedExpressions == null) { |
| optimizedClonedExpressions = new IdentityHashMap<>(); |
| } |
| |
| optimizedClonedExpressions.put(originalKey, optimizedValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * Computes all aliases which will appear in the FROM clause. |
| */ |
| public void computeTables() { |
| // Compute tables should never defer to computeTablesFromTables |
| // This iterator will pull all the table aliases out of an expression, and |
| // put them in a map. |
| ExpressionIterator iterator = new ExpressionIterator() { |
| @Override |
| public void iterate(Expression each) { |
| TableAliasLookup aliases = each.getTableAliases(); |
| |
| if (aliases != null) { |
| // Insure that an aliased table is only added to a single |
| // FROM clause. |
| if (!aliases.haveBeenAddedToStatement()) { |
| aliases.addToMap((Map<DatabaseTable, DatabaseTable>)getResult()); |
| aliases.setHaveBeenAddedToStatement(true); |
| } |
| } |
| } |
| }; |
| |
| iterator.setResult(new Hashtable(5)); |
| |
| if (getWhereClause() != null) { |
| iterator.iterateOn(getWhereClause()); |
| } else if (hasOuterJoinExpressions()) { |
| Expression outerJoinCriteria = getOuterJoinExpressionsHolders().get(0).joinExpression; |
| if (outerJoinCriteria != null){ |
| iterator.iterateOn(outerJoinCriteria); |
| } |
| } |
| |
| //Iterate on fields as well in that rare case where the select is not in the where clause |
| for (Object field : getFields()) { |
| if (field instanceof Expression) { |
| iterator.iterateOn((Expression)field); |
| } |
| } |
| |
| //Iterate on non-selected fields as well in that rare case where the from is not in the where clause |
| if (hasNonSelectFields()) { |
| for (Object field : getNonSelectFields()) { |
| if (field instanceof Expression) { |
| iterator.iterateOn((Expression)field); |
| } |
| } |
| } |
| |
| // Always iterator on the builder, as the where clause may not contain the builder, i.e. value=value. |
| iterator.iterateOn(getBuilder()); |
| |
| Map<DatabaseTable, DatabaseTable> allTables = (Map<DatabaseTable, DatabaseTable>)iterator.getResult(); |
| setTableAliases(allTables); |
| |
| for (DatabaseTable table : allTables.values()) { |
| addTable(table); |
| } |
| } |
| |
| /** |
| * If there is no where clause, alias the tables from the tables list directly. Assume there's |
| * no ambiguity |
| */ |
| public void computeTablesFromTables() { |
| Map<DatabaseTable, DatabaseTable> allTables = new Hashtable(); |
| AsOfClause asOfClause = null; |
| |
| if (getBuilder().hasAsOfClause() && !getBuilder().getSession().getProject().hasGenericHistorySupport()) { |
| asOfClause = getBuilder().getAsOfClause(); |
| } |
| |
| for (int index = 0; index < getTables().size(); index++) { |
| DatabaseTable next = getTables().get(index); |
| |
| // Aliases in allTables must now be decorated database tables. |
| DatabaseTable alias = new DecoratedDatabaseTable("t" + (index), asOfClause); |
| allTables.put(alias, next); |
| } |
| |
| setTableAliases(allTables); |
| } |
| |
| /** |
| * ADVANCED: |
| * If a distinct has been set the DISTINCT clause will be printed. |
| * This is used internally by TopLink for batch reading but may also be |
| * used directly for advanced queries or report queries. |
| */ |
| public void dontUseDistinct() { |
| setDistinctState(ObjectLevelReadQuery.DONT_USE_DISTINCT); |
| } |
| |
| /** |
| * Check if the field from the field expression is already contained in the select clause of the statement. |
| * This is used on order by expression when the field being ordered by must be in the select, |
| * but cannot be in the select twice. |
| */ |
| protected boolean fieldsContainField(List fields, Expression expression) { |
| DatabaseField orderByField; |
| |
| if (expression instanceof DataExpression) { |
| orderByField = ((DataExpression)expression).getField(); |
| } else { |
| return false; |
| } |
| |
| //check all fields for a match |
| for (Object fieldOrExpression : fields) { |
| if (fieldOrExpression instanceof DatabaseField) { |
| DatabaseField field = (DatabaseField)fieldOrExpression; |
| DataExpression exp = (DataExpression)expression; |
| |
| if (field.equals(orderByField)) { |
| // Ignore aggregates |
| while (((DataExpression)exp.getBaseExpression()).getMapping() instanceof AggregateObjectMapping) { |
| exp = (DataExpression)exp.getBaseExpression(); |
| } |
| if (exp.getBaseExpression() == getBuilder()) { |
| // found a match |
| return true; |
| } |
| } |
| } |
| // For CR#2589. This method was not getting the fields in the same way that |
| // printSQL does (i.e. using getFields() instead of getField()). |
| // The problem was that getField() on an expression builder led to a null pointer |
| // exception. |
| else if (fieldOrExpression != null){ |
| Expression exp = (Expression)fieldOrExpression; |
| DatabaseTable table = orderByField.getTable(); |
| |
| if (exp.getFields().contains(orderByField) && (expression.aliasForTable(table).equals(exp.aliasForTable(table)))) { |
| //found a match |
| return true; |
| } |
| } |
| } |
| // no matches |
| return false; |
| } |
| |
| /** |
| * Gets a unique id that will be used to alias the next table. |
| * For sub-selects all must use this same aliasing information, maintained |
| * in the root enclosing statement. For CR#2627019 |
| */ |
| public int getCurrentAliasNumber() { |
| if (getParentStatement() != null) { |
| return getParentStatement().getCurrentAliasNumber(); |
| } else { |
| return currentAliasNumber; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return all the fields |
| */ |
| public Vector getFields() { |
| return fields; |
| } |
| |
| protected ForUpdateClause getForUpdateClause() { |
| return forUpdateClause; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the group bys. |
| */ |
| public List<Expression> getGroupByExpressions() { |
| if (groupByExpressions == null) { |
| groupByExpressions = new ArrayList<>(); |
| } |
| |
| return groupByExpressions; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the having expression. |
| */ |
| public Expression getHavingExpression() { |
| return havingExpression; |
| } |
| |
| /** |
| * INTERNAL: |
| * Query held as it may store properties needed to generate the SQL |
| */ |
| public ReadQuery getQuery() { |
| return this.query; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the StartWith expression |
| */ |
| public Expression getStartWithExpression() { |
| return startWithExpression; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the CONNECT BY expression |
| */ |
| public Expression getConnectByExpression() { |
| return connectByExpression; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the ORDER SIBLINGS BY expression |
| */ |
| public List<Expression> getOrderSiblingsByExpressions() { |
| return orderSiblingsByExpressions; |
| } |
| |
| /** |
| * INTERNAL: |
| * @return the position of the PRIOR keyword |
| */ |
| public ReadAllQuery.Direction getDirection() { |
| return direction; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the next value of fieldCounter |
| */ |
| public int getNextFieldCounterValue(){ |
| return ++fieldCounter; |
| } |
| |
| /** |
| * Return the fields we don't want to select but want to join on. |
| */ |
| public List<Object> getNonSelectFields() { |
| return nonSelectFields; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the order expressions for the query. |
| */ |
| public List<Expression> getOrderByExpressions() { |
| if (orderByExpressions == null) { |
| orderByExpressions = new ArrayList(4); |
| } |
| |
| return orderByExpressions; |
| } |
| |
| public List<Expression> getUnionExpressions() { |
| if (unionExpressions == null) { |
| unionExpressions = new ArrayList(4); |
| } |
| return unionExpressions; |
| } |
| |
| public void setUnionExpressions(List<Expression> unionExpressions) { |
| this.unionExpressions = unionExpressions; |
| } |
| |
| /** |
| * INTERNAL: |
| * returns outerJoinExpressionHolders representing outerjoin expressions. |
| */ |
| public List<OuterJoinExpressionHolder> getOuterJoinExpressionsHolders() { |
| if (outerJoinExpressionHolders == null) { |
| outerJoinExpressionHolders = new ArrayList(4); |
| } |
| |
| return outerJoinExpressionHolders; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used by ExpressionBuilder and QueryKeyExpression normalization to create a standard outerjoin. |
| * @param joinExpression - expression resulting in the outerjoin. Null if it is for inheritance reading of subclasses |
| * @param outerJoinedMappingCriteria - used for querykey mapping expressions |
| * @param outerJoinedAdditionalJoinCriteria - additional tables/expressions to join. Usually for multitableInheritance join expressions |
| * @param descriptor - descriptor to use if this is for reading in subclasses in one query. |
| */ |
| public Integer addOuterJoinExpressionsHolders(ObjectExpression joinExpression, Expression outerJoinedMappingCriteria, |
| Map<DatabaseTable, Expression> outerJoinedAdditionalJoinCriteria, ClassDescriptor descriptor) { |
| |
| int index = getOuterJoinExpressionsHolders().size(); |
| OuterJoinExpressionHolder holder = new OuterJoinExpressionHolder(this, joinExpression, outerJoinedMappingCriteria, |
| outerJoinedAdditionalJoinCriteria, descriptor); |
| |
| getOuterJoinExpressionsHolders().add(holder); |
| return index; |
| } |
| |
| /** |
| * INTERNAL: |
| * used by TREAT to add in a join from the parent table to the child tables when |
| * the parent expression did not add an outer join of its own |
| */ |
| public Integer addOuterJoinExpressionsHolders(Map<DatabaseTable, Expression> outerJoinedAdditionalJoinCriteria, ClassDescriptor descriptor) { |
| |
| List<OuterJoinExpressionHolder> outerJoinExpressionHolders = getOuterJoinExpressionsHolders(); |
| int index = outerJoinExpressionHolders.size(); |
| OuterJoinExpressionHolder holder = new OuterJoinExpressionHolder(this, null, null, |
| outerJoinedAdditionalJoinCriteria, descriptor) { |
| @Override |
| protected void process(boolean usesHistory, boolean isMapKeyHolder) { |
| sourceTable = descriptor.getTables().get(0); |
| int count = 0; |
| for (Map.Entry<DatabaseTable, Expression> entry: outerJoinedAdditionalJoinCriteria.entrySet()) { |
| DatabaseTable table = entry.getKey(); |
| Expression onExpression = entry.getValue(); |
| if (count==0) { |
| targetTable = table; |
| sourceAlias = onExpression.aliasForTable(sourceTable); |
| targetAlias = onExpression.aliasForTable(targetTable); |
| } |
| |
| 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 descriptor's own table - true; otherwise (it's child's table) - false. |
| this.additionalTargetIsDescriptorTable.add(false); |
| } |
| count++; |
| } |
| |
| if(usesHistory) { |
| sourceTable = getTableAliases().get(sourceAlias); |
| targetTable = getTableAliases().get(targetAlias); |
| } |
| } |
| }; |
| |
| outerJoinExpressionHolders.add(holder); |
| return index; |
| } |
| |
| /** |
| * Return the parent statement if using subselects. |
| * This is used to normalize correctly with subselects. |
| */ |
| public SQLSelectStatement getParentStatement() { |
| return parentStatement; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the aliases used. |
| */ |
| public Map<DatabaseTable, DatabaseTable> getTableAliases() { |
| return tableAliases; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return all the tables. |
| */ |
| public List<DatabaseTable> getTables() { |
| return tables; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return True if unique field aliases will be generated of the form |
| * "fieldname AS fieldnameX", False otherwise. |
| */ |
| public boolean getUseUniqueFieldAliases(){ |
| return this.useUniqueFieldAliases; |
| } |
| |
| protected boolean hasAliasForTable(DatabaseTable table) { |
| if (tableAliases != null) { |
| return getTableAliases().containsKey(table); |
| } |
| |
| return false; |
| } |
| |
| public boolean hasGroupByExpressions() { |
| return (groupByExpressions != null) && (!groupByExpressions.isEmpty()); |
| } |
| |
| public boolean hasHavingExpression() { |
| return (havingExpression != null); |
| } |
| |
| public boolean hasStartWithExpression() { |
| return startWithExpression != null; |
| } |
| |
| public boolean hasConnectByExpression() { |
| return connectByExpression != null; |
| } |
| |
| public boolean hasOrderSiblingsByExpressions() { |
| return (orderSiblingsByExpressions != null) && (!orderSiblingsByExpressions.isEmpty()); |
| } |
| |
| public boolean hasHierarchicalQueryExpressions() { |
| return ((startWithExpression != null) || (connectByExpression != null) || ((orderSiblingsByExpressions != null) && (!orderSiblingsByExpressions.isEmpty()))); |
| } |
| |
| public boolean hasOrderByExpressions() { |
| return (orderByExpressions != null) && (!orderByExpressions.isEmpty()); |
| } |
| |
| public boolean hasUnionExpressions() { |
| return (unionExpressions != null) && (!unionExpressions.isEmpty()); |
| } |
| |
| public boolean hasNonSelectFields() { |
| return (nonSelectFields != null) && (!nonSelectFields.isEmpty()); |
| } |
| |
| public boolean hasOuterJoinExpressions() { |
| return (outerJoinExpressionHolders != null) && (!outerJoinExpressionHolders.isEmpty()); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| public boolean isAggregateSelect() { |
| return isAggregateSelect; |
| } |
| |
| /** |
| * INTERNAL: |
| * return true if this query has computed its distinct value already |
| */ |
| public boolean isDistinctComputed() { |
| return distinctState != ObjectLevelReadQuery.UNCOMPUTED_DISTINCT; |
| } |
| |
| /** |
| * INTERNAL: |
| * Normalize an expression into a printable structure. |
| * i.e. merge the expression with the join expressions. |
| * Also replace table names with corresponding aliases. |
| */ |
| public final void normalize(AbstractSession session, ClassDescriptor descriptor) { |
| // 2612538 - the default size of Map (32) is appropriate |
| normalize(session, descriptor, new IdentityHashMap()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Normalize an expression into a printable structure. |
| * i.e. merge the expression with the join expressions. |
| * Also replace table names with corresponding aliases. |
| * @param clonedExpressions With 2612185 allows additional expressions |
| * from multiple bases to be rebuilt on the correct cloned base. |
| */ |
| public void normalize(AbstractSession session, ClassDescriptor descriptor, Map clonedExpressions) { |
| // Initialize the builder. |
| if (getBuilder() == null) { |
| if (getWhereClause() == null) { |
| setBuilder(new ExpressionBuilder()); |
| } else { |
| setBuilder(getWhereClause().getBuilder()); |
| } |
| } |
| |
| ExpressionBuilder builder = getBuilder(); |
| |
| // For flashback at this point make the expression |
| // as of the correct time. |
| if ((session.getAsOfClause() != null) && !isSubSelect()) { |
| getWhereClause().asOf(session.getAsOfClause()); |
| } else if (builder.hasAsOfClause() && builder.getAsOfClause().isUniversal()) { |
| // An as of clause set at the query level. |
| getWhereClause().asOf(((UniversalAsOfClause)builder.getAsOfClause()).getAsOfClause()); |
| } |
| |
| // For flashback: The builder is increasingly important. It can store |
| // an AsOfClause, needs to be normalized for history, and aliased for |
| // pessimistic locking to work. Hence everything that would have |
| // been applied to the where clause will be applied to the builder if |
| // the former is null. In the past though if there was no where |
| // clause just threw away the builder (to get to this point we had to |
| // pass it in via the vacated where clause), and neither normalized nor |
| // aliased it directly. |
| if (getWhereClause() == builder) { |
| setWhereClause(null); |
| } |
| |
| builder.setSession(session.getRootSession(null)); |
| |
| // Some queries are not on objects but for data, thus no descriptor. |
| if (!builder.doesNotRepresentAnObjectInTheQuery()) { |
| if (descriptor != null) { |
| Class queryClass = builder.getQueryClass(); |
| // GF 2333 Only change the descriptor class if: |
| // 1 - it is not set |
| // 2 - if this is an inheritance query |
| // 3 - if it is to a table per tenant multitenant descriptor. |
| // When used at the EM level we need to ensure we are |
| // normalizing against the initialized descriptor and not |
| // that of the server session which is uninitialized. |
| if ((queryClass == null) || descriptor.isChildDescriptor() || descriptor.hasTablePerMultitenantPolicy()) { |
| builder.setQueryClassAndDescriptor(descriptor.getJavaClass(), descriptor); |
| } |
| } |
| } |
| |
| // Compute all other expressions used other than the where clause, i.e. select, order by, group by. |
| // Also must ensure that all expression use a unique builder. |
| Vector allExpressions = new Vector(); |
| |
| // Process select expressions. |
| rebuildAndAddExpressions(getFields(), allExpressions, builder, clonedExpressions); |
| |
| // Process non-select expressions |
| if (hasNonSelectFields()) { |
| rebuildAndAddExpressions(getNonSelectFields(), allExpressions, builder, clonedExpressions); |
| } |
| |
| // Process group by expressions. |
| if (hasGroupByExpressions()) { |
| rebuildAndAddExpressions(getGroupByExpressions(), allExpressions, builder, clonedExpressions); |
| } |
| |
| // Process union expressions. |
| if (hasUnionExpressions()) { |
| rebuildAndAddExpressions(getUnionExpressions(), allExpressions, builder, clonedExpressions); |
| } |
| |
| if (hasHavingExpression()) { |
| //rebuildAndAddExpressions(getHavingExpression(), allExpressions, builder, clonedExpressions); |
| Expression expression = getHavingExpression(); |
| ExpressionBuilder originalBuilder = expression.getBuilder(); |
| if (originalBuilder != builder) { |
| // For bug 2612185 avoid rebuildOn if possible as it rebuilds all on a single base. |
| // i.e. Report query items could be from parallel expressions. |
| if (clonedExpressions.get(originalBuilder) != null) { |
| expression = expression.copiedVersionFrom(clonedExpressions); |
| } else { |
| // Possibly the expression was built with the wrong builder. |
| expression = expression.rebuildOn(builder); |
| } |
| |
| setHavingExpression(expression); |
| } |
| |
| allExpressions.add(expression); |
| } |
| |
| // Process order by expressions. |
| if (hasOrderByExpressions()) { |
| normalizeOrderBy(builder, allExpressions, clonedExpressions, session); |
| } |
| |
| // Process outer join by expressions. |
| if (hasOuterJoinExpressions()) { |
| for (OuterJoinExpressionHolder holder : this.getOuterJoinExpressionsHolders()) { |
| if (holder.outerJoinedMappingCriteria != null) { |
| Expression expression = rebuildExpression(holder.outerJoinedMappingCriteria, builder, clonedExpressions); |
| if (holder.outerJoinedMappingCriteria != expression) { |
| holder.outerJoinedMappingCriteria = expression; |
| } |
| allExpressions.add(expression); |
| } |
| if (holder.outerJoinedAdditionalJoinCriteria != null) { |
| rebuildAndAddExpressions(holder.outerJoinedAdditionalJoinCriteria, allExpressions, builder, clonedExpressions); |
| } |
| } |
| } |
| |
| //Process hierarchical query expressions. |
| if (hasStartWithExpression()) { |
| startWithExpression = getStartWithExpression().rebuildOn(builder); |
| allExpressions.add(startWithExpression); |
| } |
| |
| if (hasConnectByExpression()) { |
| connectByExpression = getConnectByExpression().rebuildOn(builder); |
| } |
| |
| if (hasOrderSiblingsByExpressions()) { |
| rebuildAndAddExpressions(getOrderSiblingsByExpressions(), allExpressions, builder, clonedExpressions); |
| } |
| |
| // We have to handle the cases where the where |
| // clause is initially empty but might have clauses forced into it because the class |
| // has multiple tables, order by forces a join, etc. So we have to create a builder |
| // and add expressions for it and the extras, but throw it away if they didn't force anything |
| Expression oldRoot = getWhereClause(); |
| ExpressionNormalizer normalizer = new ExpressionNormalizer(this); |
| normalizer.setSession(session); |
| normalizer.setClonedExpressions(clonedExpressions); |
| boolean isDistinctComputed = isDistinctComputed(); |
| |
| Expression newRoot = null; |
| |
| if (oldRoot != null) { |
| newRoot = oldRoot.normalize(normalizer); |
| } |
| |
| // CR#3166542 always ensure that the builder has been normalized, |
| // there may be an expression that does not refer to the builder, i.e. value=value. |
| if (descriptor != null) { |
| builder.normalize(normalizer); |
| } |
| |
| for (int index = 0; index < allExpressions.size(); index++) { |
| Expression expression = (Expression)allExpressions.get(index); |
| expression.getBuilder().setSession(session); |
| expression.normalize(normalizer); |
| } |
| // distinct state has been set by normalization, see may be that should be reversed |
| if (shouldDistinctBeUsed() && !isDistinctComputed && !session.getPlatform().isLobCompatibleWithDistinct()) { |
| for (Object field : getFields()) { |
| if (field instanceof DatabaseField) { |
| if (Helper.isLob((DatabaseField)field)) { |
| dontUseDistinct(); |
| break; |
| } |
| } else if (field instanceof Expression) { |
| if (Helper.hasLob(((Expression)field).getSelectionFields(this.query))) { |
| dontUseDistinct(); |
| break; |
| } |
| } |
| } |
| } |
| |
| // Sets the where clause and AND's it with the additional Expression |
| // setNormalizedWhereClause must be called to avoid the builder side-effects |
| if (newRoot == null) { |
| setNormalizedWhereClause(normalizer.getAdditionalExpression()); |
| } else { |
| setNormalizedWhereClause(newRoot.and(normalizer.getAdditionalExpression())); |
| } |
| |
| if (getWhereClause() != null) { |
| allExpressions.add(getWhereClause()); |
| } |
| |
| // CR#3166542 always ensure that the builder has been normalized, |
| // there may be an expression that does not refer to the builder, i.e. value=value. |
| if (descriptor != null) { |
| allExpressions.add(builder); |
| } |
| |
| // Must also assign aliases to outer joined mapping criterias. |
| if (hasOuterJoinExpressions()) { |
| // Check for null on criterias. |
| for (OuterJoinExpressionHolder holder : this.outerJoinExpressionHolders) { |
| Expression criteria = holder.outerJoinedMappingCriteria;// |
| if (criteria != null) { |
| allExpressions.add(criteria); |
| } |
| |
| Map<DatabaseTable, Expression> map = holder.outerJoinedAdditionalJoinCriteria; |
| if (map != null) { |
| Iterator<Expression> it = map.values().iterator(); |
| while(it.hasNext()) { |
| criteria = it.next(); |
| if(criteria != null) { |
| allExpressions.add(criteria); |
| } |
| } |
| } |
| } |
| } |
| |
| // Bug 2956674 Remove validate call as validation will be completed as the expression was normalized |
| // Assign all table aliases. |
| assignAliases(allExpressions); |
| |
| // If this is data level then the tables must be set manually. |
| if (descriptor == null) { |
| computeTablesFromTables(); |
| } else { |
| computeTables(); |
| } |
| |
| // Now that the parent statement has been normalized, aliased, etc., |
| // normalize the subselect expressions. For CR#4223. |
| if (normalizer.encounteredSubSelectExpressions()) { |
| normalizer.normalizeSubSelects(clonedExpressions); |
| } |
| |
| // When a distinct is used the order bys must be in the select clause, so this forces them into the select. |
| if (hasOrderByExpressions()) { |
| // CR2114; If this is data level then we don't have a descriptor. |
| // We don't have a target class so we must use the root platform. PWK |
| // We are not fixing the informix. |
| Class queryClass = null; |
| if (descriptor != null) { |
| queryClass = descriptor.getJavaClass(); |
| } |
| DatasourcePlatform platform = (DatasourcePlatform)session.getPlatform(queryClass); |
| if (platform.shouldSelectIncludeOrderBy() || (shouldDistinctBeUsed() && platform.shouldSelectDistinctIncludeOrderBy())) { |
| addOrderByExpressionToSelectForDistinct(); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Normalize an expression mapping all of the descriptor's tables to the view. |
| * This is used to allow a descriptor to read from a view, but write to tables. |
| * This is used in the multiple table and subclasses read so all of the descriptor's |
| * possible tables must be mapped to the view. |
| */ |
| public void normalizeForView(AbstractSession theSession, ClassDescriptor theDescriptor, Map clonedExpressions) { |
| ExpressionBuilder builder; |
| |
| // bug 3878553 - alias all view selects. |
| setRequiresAliases(true); |
| |
| if (getWhereClause() != null) { |
| builder = getWhereClause().getBuilder(); |
| } else { |
| builder = new ExpressionBuilder(); |
| setBuilder(builder); |
| } |
| |
| builder.setViewTable(getTables().get(0)); |
| |
| normalize(theSession, theDescriptor, clonedExpressions); |
| } |
| |
| |
| /** |
| * Check the order by for object expressions. |
| * Order by the objects primary key or all fields for aggregates. |
| */ |
| protected void normalizeOrderBy(Expression builder, List<Expression> allExpressions, Map<Expression, Expression> clonedExpressions, AbstractSession session) { |
| List<Expression> newOrderBys = new ArrayList(this.orderByExpressions.size()); |
| for (Expression orderBy : this.orderByExpressions) { |
| orderBy = rebuildExpression(orderBy, builder, clonedExpressions); |
| Expression base = orderBy; |
| Boolean asc = null; |
| Boolean nullsFirst = null; |
| if (orderBy.isFunctionExpression()) { |
| if (base.getOperator().getSelector() == ExpressionOperator.NullsFirst) { |
| nullsFirst = true; |
| base = ((FunctionExpression)base).getChildren().get(0); |
| } else if (base.getOperator().getSelector() == ExpressionOperator.NullsLast) { |
| nullsFirst = false; |
| base = ((FunctionExpression)base).getChildren().get(0); |
| } |
| if (base.isFunctionExpression()) { |
| if (base.getOperator().getSelector() == ExpressionOperator.Ascending) { |
| asc = true; |
| base = ((FunctionExpression)base).getChildren().get(0); |
| } else if (base.getOperator().getSelector() == ExpressionOperator.Descending) { |
| asc = false; |
| base = ((FunctionExpression)base).getChildren().get(0); |
| } |
| } |
| } |
| if (base.isObjectExpression()) { |
| ObjectExpression expression = (ObjectExpression)base; |
| expression.getBuilder().setSession(session); |
| List<Expression> orderBys = null; |
| if (expression.getMapping() != null) { |
| // Check if a non basic mapping. |
| orderBys = expression.getMapping().getOrderByNormalizedExpressions(expression); |
| } else if (base.isExpressionBuilder()) { |
| orderBys = new ArrayList(expression.getDescriptor().getPrimaryKeyFields().size()); |
| for (DatabaseField field : expression.getDescriptor().getPrimaryKeyFields()) { |
| orderBys.add(expression.getField(field)); |
| } |
| } |
| if (orderBys != null) { |
| for (Expression mappingOrderBy : orderBys) { |
| if (asc != null) { |
| if (asc) { |
| mappingOrderBy = mappingOrderBy.ascending(); |
| } else { |
| mappingOrderBy = mappingOrderBy.descending(); |
| } |
| } |
| if (nullsFirst != null) { |
| if (nullsFirst) { |
| mappingOrderBy = mappingOrderBy.nullsFirst(); |
| } else { |
| mappingOrderBy = mappingOrderBy.nullsLast(); |
| } |
| } |
| newOrderBys.add(mappingOrderBy); |
| allExpressions.add(mappingOrderBy); |
| } |
| continue; |
| } |
| } |
| newOrderBys.add(orderBy); |
| allExpressions.add(orderBy); |
| } |
| this.orderByExpressions = newOrderBys; |
| } |
| |
| /** |
| * Print the SQL representation of the statement on a stream. |
| */ |
| public Vector printSQL(ExpressionSQLPrinter printer) { |
| try { |
| Vector selectFields = null; |
| printer.setRequiresDistinct(shouldDistinctBeUsed()); |
| |
| if (hasUnionExpressions()) { |
| // Ensure union order using brackets. |
| int size = getUnionExpressions().size(); |
| for (int index = 0; index < size; index++) { |
| printer.printString("("); |
| } |
| } |
| printer.printString("SELECT "); |
| |
| if (getHintString() != null) { |
| printer.printString(getHintString()); |
| printer.printString(" "); |
| } |
| |
| if (shouldDistinctBeUsed()) { |
| printer.printString("DISTINCT "); |
| } |
| |
| selectFields = writeFieldsIn(printer); |
| //fix bug:6070214: turn off unique field aliases after fields are written |
| setUseUniqueFieldAliases(false); |
| |
| appendFromClauseToWriter(printer); |
| |
| if (!(getWhereClause() == null)) { |
| printer.printString(" WHERE "); |
| printer.printExpression(getWhereClause()); |
| } |
| |
| if (hasHierarchicalQueryExpressions()) { |
| appendHierarchicalQueryClauseToWriter(printer); |
| } |
| |
| if (hasGroupByExpressions()) { |
| appendGroupByClauseToWriter(printer); |
| } |
| if (hasHavingExpression()) { |
| //appendHavingClauseToWriter(printer); |
| printer.printString(" HAVING "); |
| printer.printExpression(getHavingExpression()); |
| } |
| |
| if (hasOrderByExpressions()) { |
| appendOrderClauseToWriter(printer); |
| } |
| |
| if(printer.getPlatform().shouldPrintLockingClauseAfterWhereClause() && printer.getPlatform().shouldPrintForUpdateClause()) { |
| // For pessimistic locking. |
| appendForUpdateClause(printer); |
| } |
| |
| if (hasUnionExpressions()) { |
| appendUnionClauseToWriter(printer); |
| } |
| |
| return selectFields; |
| } catch (IOException exception) { |
| throw ValidationException.fileError(exception); |
| } |
| } |
| |
| /** |
| * Rebuild the expressions with the correct expression builder if using a different one. |
| */ |
| public void rebuildAndAddExpressions(List expressions, List allExpressions, ExpressionBuilder primaryBuilder, Map clonedExpressions) { |
| for (int index = 0; index < expressions.size(); index++) { |
| Object fieldOrExpression = expressions.get(index); |
| // Allow for special fields that contain a functional transformation. |
| if (fieldOrExpression instanceof FunctionField) { |
| fieldOrExpression = ((FunctionField)fieldOrExpression).getExpression(); |
| } |
| if (fieldOrExpression instanceof Expression) { |
| Expression expression = rebuildExpression((Expression)fieldOrExpression, primaryBuilder, clonedExpressions); |
| if (fieldOrExpression != expression) { |
| expressions.set(index, expression); |
| } |
| allExpressions.add(expression); |
| } |
| } |
| } |
| |
| /** |
| * Rebuild the expression if required. |
| */ |
| public Expression rebuildExpression(Expression expression, Expression primaryBuilder, Map<Expression, Expression> clonedExpressions) { |
| ExpressionBuilder originalBuilder = expression.getBuilder(); |
| |
| if (originalBuilder != primaryBuilder) { |
| // For bug 2612185 avoid rebuildOn if possible as it rebuilds all on a single base. |
| // i.e. Report query items could be from parallel expressions. |
| if (clonedExpressions.get(originalBuilder) != null) { |
| expression = expression.copiedVersionFrom(clonedExpressions); |
| //if there is no builder or it is a copy of the base builder then rebuild otherwise it is a parallel expression not joined |
| } |
| if (originalBuilder.wasQueryClassSetInternally()) { |
| // Possibly the expression was built with the wrong builder. |
| expression = expression.rebuildOn(primaryBuilder); |
| } |
| } |
| return expression; |
| } |
| |
| /** |
| * Rebuild the expressions with the correct expression builder if using a different one. |
| * Exact copy of the another rebuildAndAddExpressions adopted to a Map with Expression values |
| * as the first parameter (instead of Vector in the original method) |
| */ |
| public void rebuildAndAddExpressions(Map expressions, Vector allExpressions, ExpressionBuilder primaryBuilder, Map clonedExpressions) { |
| Iterator it = expressions.entrySet().iterator(); |
| while (it.hasNext()) { |
| Map.Entry entry = (Map.Entry)it.next(); |
| Object fieldOrExpression = entry.getValue(); |
| |
| if (fieldOrExpression instanceof Expression) { |
| Expression expression = (Expression)fieldOrExpression; |
| ExpressionBuilder originalBuilder = expression.getBuilder(); |
| |
| if (originalBuilder != primaryBuilder) { |
| // For bug 2612185 avoid rebuildOn if possible as it rebuilds all on a single base. |
| // i.e. Report query items could be from parallel expressions. |
| if (clonedExpressions.get(originalBuilder) != null) { |
| expression = expression.copiedVersionFrom(clonedExpressions); |
| //if there is no builder or it is a copy of the base builder then rebuild otherwise it is a parallel expression not joined |
| } |
| if (originalBuilder.wasQueryClassSetInternally()) { |
| // Possibly the expression was built with the wrong builder. |
| expression = expression.rebuildOn(primaryBuilder); |
| } |
| |
| entry.setValue(expression); |
| } |
| |
| allExpressions.addElement(expression); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| public void removeField(DatabaseField field) { |
| getFields().remove(field); |
| } |
| |
| /** |
| * Remove a table from the statement. The table will |
| * be dropped from the FROM part of the SQL statement. |
| */ |
| public void removeTable(DatabaseTable table) { |
| getTables().remove(table); |
| } |
| |
| /** |
| * INTERNAL: Returns true if aliases are required, false otherwise. |
| * If requiresAliases is set then force aliasing, this is required for object-rel. |
| */ |
| public boolean requiresAliases() { |
| if (requiresAliases || hasOuterJoinExpressions()) { |
| return true; |
| } |
| |
| if (tableAliases != null) { |
| return getTableAliases().size() > 1; |
| } |
| |
| // tableAliases is null |
| return false; |
| } |
| |
| /** |
| * ADVANCED: |
| * If a distinct has been set the DISTINCT clause will be printed. |
| * This is used internally by TopLink for batch reading but may also be |
| * used directly for advanced queries or report queries. |
| */ |
| public void resetDistinct() { |
| setDistinctState(ObjectLevelReadQuery.UNCOMPUTED_DISTINCT); |
| } |
| |
| @Override |
| public void setBuilder(ExpressionBuilder builder){ |
| this.builder = builder; |
| } |
| |
| /** |
| * Sets a unique id that will be used to alias the next table. |
| * For sub-selects all must use this same aliasing information, maintained |
| * in the root enclosing statement. For CR#2627019 |
| */ |
| public void setCurrentAliasNumber(int currentAliasNumber) { |
| if (getParentStatement() != null) { |
| getParentStatement().setCurrentAliasNumber(currentAliasNumber); |
| } else { |
| this.currentAliasNumber = currentAliasNumber; |
| } |
| } |
| |
| /** |
| * Set the non select fields. The fields are used only on joining. |
| */ |
| public void setNonSelectFields(List nonSelectFields) { |
| this.nonSelectFields = nonSelectFields; |
| } |
| |
| /** |
| * Set the where clause expression. |
| * This must be used during normalization as the normal setWhereClause has the side effect |
| * of setting the builder, which must not occur during normalize. |
| */ |
| public void setNormalizedWhereClause(Expression whereClause) { |
| this.whereClause = whereClause; |
| } |
| |
| /** |
| * ADVANCED: |
| * If a distinct has been set the DISTINCT clause will be printed. |
| * This is used internally by TopLink for batch reading but may also be |
| * used directly for advanced queries or report queries. |
| */ |
| public void setDistinctState(short distinctState) { |
| this.distinctState = distinctState; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the fields, if any are aggregate selects then record this so that the distinct is not printed through anyOfs. |
| */ |
| public void setFields(Vector fields) { |
| for (Object fieldOrExpression : fields) { |
| if (fieldOrExpression instanceof FunctionExpression) { |
| if (((FunctionExpression)fieldOrExpression).getOperator().isAggregateOperator()) { |
| setIsAggregateSelect(true); |
| break; |
| } |
| } |
| } |
| this.fields = fields; |
| } |
| |
| public void setGroupByExpressions(List<Expression> expressions) { |
| this.groupByExpressions = expressions; |
| } |
| |
| public void setHavingExpression(Expression expressions) { |
| this.havingExpression = expressions; |
| } |
| |
| /** |
| * INTERNAL: |
| * takes the hierarchical query expression which have been set on the query and sets them here |
| * used to generate the Hierarchical Query Clause in the SQL |
| */ |
| public void setHierarchicalQueryExpressions(Expression startWith, Expression connectBy, List<Expression> orderSiblingsExpressions) { |
| setHierarchicalQueryExpressions(startWith, connectBy, orderSiblingsExpressions, null); |
| } |
| |
| /** |
| * INTERNAL: |
| * takes the hierarchical query expression which have been set on the query and sets them here |
| * used to generate the Hierarchical Query Clause in the SQL |
| */ |
| public void setHierarchicalQueryExpressions(Expression startWith, Expression connectBy, List<Expression> orderSiblingsExpressions, ReadAllQuery.Direction direction) { |
| this.startWithExpression = startWith; |
| this.connectByExpression = connectBy; |
| this.orderSiblingsByExpressions = orderSiblingsExpressions; |
| this.direction = direction; |
| } |
| |
| public void setIsAggregateSelect(boolean isAggregateSelect) { |
| this.isAggregateSelect = isAggregateSelect; |
| } |
| |
| protected void setForUpdateClause(ForUpdateClause clause) { |
| this.forUpdateClause = clause; |
| } |
| |
| public void setLockingClause(ForUpdateClause lockingClause) { |
| this.forUpdateClause = lockingClause; |
| } |
| |
| public void setOrderByExpressions(List<Expression> orderByExpressions) { |
| this.orderByExpressions = orderByExpressions; |
| } |
| |
| /** |
| * Set the parent statement if using subselects. |
| * This is used to normalize correctly with subselects. |
| */ |
| public void setParentStatement(SQLSelectStatement parentStatement) { |
| this.parentStatement = parentStatement; |
| } |
| |
| /** |
| * Query held as it may store properties needed to generate the SQL |
| */ |
| public void setQuery(ReadQuery query) { |
| this.query = query; |
| } |
| |
| public void setRequiresAliases(boolean requiresAliases) { |
| this.requiresAliases = requiresAliases; |
| } |
| |
| protected void setTableAliases(Map<DatabaseTable, DatabaseTable> theTableAliases) { |
| tableAliases = theTableAliases; |
| } |
| |
| public void setTables(List<DatabaseTable> theTables) { |
| tables = theTables; |
| } |
| |
| /** |
| * INTERNAL: |
| * If set unique field aliases will be generated of the form |
| * "fieldname AS fieldnameX" |
| * Where fieldname is the column name and X is an incremental value |
| * ensuring uniqueness |
| */ |
| public void setUseUniqueFieldAliases(boolean useUniqueFieldAliases){ |
| this.useUniqueFieldAliases = useUniqueFieldAliases; |
| } |
| |
| /** |
| * INTERNAL: |
| * If a distinct has been set the DISTINCT clause will be printed. |
| * This is required for batch reading. |
| */ |
| public boolean shouldDistinctBeUsed() { |
| return distinctState == ObjectLevelReadQuery.USE_DISTINCT; |
| } |
| |
| /** |
| * ADVANCED: |
| * If a distinct has been set the DISTINCT clause will be printed. |
| * This is used internally by TopLink for batch reading but may also be |
| * used directly for advanced queries or report queries. |
| */ |
| public void useDistinct() { |
| setDistinctState(ObjectLevelReadQuery.USE_DISTINCT); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected void writeField(ExpressionSQLPrinter printer, DatabaseField field) { |
| //print ", " before each selected field except the first one |
| if (printer.isFirstElementPrinted()) { |
| printer.printString(", "); |
| } else { |
| printer.setIsFirstElementPrinted(true); |
| } |
| |
| if (printer.shouldPrintQualifiedNames()) { |
| if (field.getTable() != lastTable) { |
| lastTable = field.getTable(); |
| currentAlias = getBuilder().aliasForTable(lastTable); |
| |
| // This is really for the special case where things were pre-aliased |
| if (currentAlias == null) { |
| currentAlias = lastTable; |
| } |
| } |
| |
| printer.printString(currentAlias.getQualifiedNameDelimited(printer.getPlatform())); |
| printer.printString("."); |
| printer.printString(field.getNameDelimited(printer.getPlatform())); |
| } else { |
| printer.printString(field.getNameDelimited(printer.getPlatform())); |
| } |
| if (this.getUseUniqueFieldAliases()){ |
| String alias = generatedAlias(field.getNameDelimited(printer.getPlatform())); |
| if (shouldCacheFieldAliases()) { |
| fieldAliases.put(field, alias); |
| } |
| printer.printString(" AS " + alias); |
| } |
| } |
| |
| private boolean shouldCacheFieldAliases() { |
| return shouldCacheFieldAliases; |
| } |
| |
| public void enableFieldAliasesCaching() { |
| fieldAliases = new HashMap<>(); |
| shouldCacheFieldAliases = true; |
| } |
| |
| public String getAliasFor(DatabaseField field) { |
| if (shouldCacheFieldAliases()) { |
| return fieldAliases.get(field); |
| } else { |
| return ""; |
| } |
| } |
| |
| /** |
| * Returns a generated alias based on the column name. If the new alias will be too long |
| * The alias is automatically truncated |
| */ |
| public String generatedAlias(String fieldName) { |
| return "a" + String.valueOf(getNextFieldCounterValue()); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected void writeFieldsFromExpression(ExpressionSQLPrinter printer, Expression expression, Vector newFields) { |
| expression.writeFields(printer, newFields, this); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected Vector writeFieldsIn(ExpressionSQLPrinter printer) { |
| this.lastTable = null; |
| |
| Vector newFields = NonSynchronizedVector.newInstance(); |
| |
| for (Object next : getFields()) { |
| // Fields can be null placeholders for fetch groups. |
| if (next != null) { |
| if (next instanceof Expression) { |
| writeFieldsFromExpression(printer, (Expression)next, newFields); |
| } else { |
| writeField(printer, (DatabaseField)next); |
| newFields.add(next); |
| } |
| } |
| } |
| return newFields; |
| } |
| |
| /** |
| * INTERNAL: |
| * The method searches for expressions that join two tables each in a given expression. |
| * Given expression and tablesInOrder and an empty SortedMap (TreeMap with no Comparator), this method |
| * populates the map with expressions corresponding to two tables |
| * keyed by an index (in tablesInOrder) of the table with the highest (of two) index; |
| * returns all the participating in at least one of the expressions. |
| * Example: |
| * expression (joining Employee to Project through m-m mapping "projects"): |
| * (employee.emp_id = proj_emp.emp_id) and (proj_emp.proj_id = project.proj_id) |
| * tablesInOrder: |
| * employee, proj_emp, project |
| * |
| * results: |
| * map: |
| * 1 -> (employee.emp_id = proj_emp.emp_id) |
| * 2 -> (proj_emp.proj_id = project.proj_id) |
| * returned SortedSet: {0, 1, 2}. |
| * |
| * Note that tablesInOrder must contain all tables used by expression |
| */ |
| public static SortedSet mapTableIndexToExpression(Expression expression, TreeMap map, List<DatabaseTable> tablesInOrder) { |
| // glassfish issue 2440: |
| // - Use DataExpression.getAliasedField instead of getField. This |
| // allows to distinguish source and target tables in case of a self |
| // referencing relationship. |
| // - Removed the block handling ParameterExpressions, because it is |
| // not possible to get into that method with a ParameterExpression. |
| TreeSet tables = new TreeSet(); |
| if(expression instanceof DataExpression) { |
| DataExpression de = (DataExpression)expression; |
| if(de.getAliasedField() != null) { |
| tables.add(tablesInOrder.indexOf(de.getAliasedField().getTable())); |
| } |
| return tables; |
| } |
| |
| // Bug 279784 - Incomplete OUTER JOIN based on JoinTable. |
| // Save a copy of the original map to accommodate cases with more than one joined field, such as: |
| // (employee.emp_id1 = proj_emp.emp_id1).and((employee.emp_id2 = proj_emp.emp_id2).and((proj_emp.proj_id1 = project.proj_id1).and(proj_emp.proj_id2 = project.proj_id2))) |
| // Never adding (always overriding) cached expression (the code before the fix) resulted in the first child (employee.emp_id1 = proj_emp.emp_id1) being overridden and lost. |
| // Always adding to the cached in the map expression would result in (proj_emp.proj_id1 = project.proj_id1).and(proj_emp.proj_id2 = project.proj_id2)) added twice. |
| TreeMap originalMap = (TreeMap)map.clone(); |
| if(expression instanceof CompoundExpression) { |
| CompoundExpression ce = (CompoundExpression)expression; |
| tables.addAll(mapTableIndexToExpression(ce.getFirstChild(), map, tablesInOrder)); |
| tables.addAll(mapTableIndexToExpression(ce.getSecondChild(), map, tablesInOrder)); |
| } else if(expression instanceof FunctionExpression) { |
| FunctionExpression fe = (FunctionExpression)expression; |
| Iterator<Expression> it = fe.getChildren().iterator(); |
| while(it.hasNext()) { |
| tables.addAll(mapTableIndexToExpression(it.next(), map, tablesInOrder)); |
| } |
| } |
| |
| if(tables.size() == 2) { |
| Object last = tables.last(); |
| Expression cachedExpression = (Expression)originalMap.get(last); |
| if(cachedExpression == null) { |
| map.put(last, expression); |
| } else { |
| map.put(last, cachedExpression.and(expression)); |
| } |
| } |
| |
| return tables; |
| } |
| |
| /** |
| * INTERNAL: |
| * The method searches for expressions that join two tables each in a given expression. |
| * Given expression and tablesInOrder, this method |
| * returns the map with expressions corresponding to two tables |
| * keyed by tables (from tablesInOrder) with the highest (of two) index; |
| * Example: |
| * expression (joining Employee to Project through m-m mapping "projects"): |
| * (employee.emp_id = proj_emp.emp_id) and (proj_emp.proj_id = project.proj_id) |
| * tablesInOrder: |
| * employee, proj_emp, project |
| * |
| * results: |
| * returned map: |
| * proj_emp -> (employee.emp_id = proj_emp.emp_id) |
| * project -> (proj_emp.proj_id = project.proj_id) |
| * |
| * Note that tablesInOrder must contain all tables used by expression |
| */ |
| public static Map mapTableToExpression(Expression expression, Vector tablesInOrder) { |
| TreeMap indexToExpressionMap = new TreeMap(); |
| mapTableIndexToExpression(expression, indexToExpressionMap, tablesInOrder); |
| HashMap map = new HashMap(indexToExpressionMap.size()); |
| Iterator it = indexToExpressionMap.entrySet().iterator(); |
| while(it.hasNext()) { |
| Map.Entry entry = (Map.Entry)it.next(); |
| int index = (Integer) entry.getKey(); |
| map.put(tablesInOrder.get(index), entry.getValue()); |
| } |
| return map; |
| } |
| |
| // Outer join support methods / classes |
| |
| /* |
| * Sort the holder list. |
| * The sorting of holders is done to make sure that |
| * for every table alias that is both source of (one or more) holders |
| * and target of another holder the "target" holder is listed |
| * before the "source" holder(s). |
| * Denoting a holder as a pair of source alias and target alias, that means: |
| * |
| * {t0, t1}, {t1, t2}, {t1, t3} or {t0, t1}, {t1, t3}, {t1, t2} is ok; |
| * but |
| * {t1, t2}, {t0, t1}, {t1, t3} or {t1, t2}, {t1, t3}, {t0, t1} should be reordered. |
| * |
| * To achieve this goal the method assigns an integer index to each table alias |
| * used by holders (for instance t0 -> 0; t1 -> 1; t2 -> 2; t3 -> 3). |
| * |
| * Each holder assigned a list of integers corresponding to a sequence of aliases |
| * that starts with the one, which no holder uses as its target alias, |
| * possibly continues several times from source alias to target alias for some other holder (if exists), |
| * and ends with the holder's target alias. |
| * |
| * {t0, t1} -> {0, 1}; |
| * {t1, t2} -> {0, 1, 2} |
| * {t1, t3} -> {0, 1, 3} |
| * |
| * Sorting of holders uses comparison of these lists (see OuterJoinExpressionHolder.compareTo): |
| * |
| * {0, 1} < {0, 1, 2} < {0, 1, 3} |
| * Therefore the holders will be ordered: |
| * {t0, t1}, {t1, t2}, {t1, t3} |
| * |
| * More complex example: |
| * {t0, t1}, {t1, t2}, {t2, t7}, {t7, t10}, {t1, t3}, {t4, t5}, {t5, t8}, {t8, t9}, {t5, t11}, {t4, t12} |
| * |
| * A holder may have additional target table(s): |
| * Examples: |
| * secondary SALARY table in Employee class; |
| * LPROJECT table in LargeProject class (primary PROJECT table is inherited from Project class). |
| * In that case each additional target alias should have an entry in targetAliasToHolders |
| * so that the holder that uses the additional table as a source could be placed in correct order. |
| * For instance: |
| * holder1 has target alias t1 and additional target alias t2: |
| * if the latter is ignored then holder2 = {t2, t3} could be placed ahead of holder1. |
| * |
| */ |
| protected void sortOuterJoinExpressionHolders(List<OuterJoinExpressionHolder> holders) { |
| Map<DatabaseTable, OuterJoinExpressionHolder> targetAliasToHolders = new HashMap(); |
| Set<DatabaseTable> aliases = new HashSet(); |
| Map<DatabaseTable, Integer> aliasToIndexes = new HashMap(aliases.size()); |
| int i = 0; |
| for(OuterJoinExpressionHolder holder : holders) { |
| targetAliasToHolders.put(holder.targetAlias, holder); |
| if(!aliases.contains(holder.sourceAlias)) { |
| aliases.add(holder.sourceAlias); |
| aliasToIndexes.put(holder.sourceAlias, i++); |
| } |
| if(!aliases.contains(holder.targetAlias)) { |
| aliases.add(holder.targetAlias); |
| aliasToIndexes.put(holder.targetAlias, i++); |
| } |
| if(holder.additionalTargetAliases != null) { |
| // if t1 is target alias and t2 is additional target alias (corresponding either to the secondary or inherited table) |
| for(DatabaseTable alias : holder.additionalTargetAliases) { |
| if(!aliases.contains(alias)) { |
| aliases.add(alias); |
| aliasToIndexes.put(alias, i++); |
| } |
| targetAliasToHolders.put(alias, holder); |
| } |
| } |
| } |
| for(OuterJoinExpressionHolder holder : holders) { |
| holder.createIndexList(targetAliasToHolders, aliasToIndexes); |
| } |
| Collections.sort(holders); |
| } |
| } |