/******************************************************************************* * Copyright (c) 1998, 2015 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 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: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.internal.expressions; import java.io.*; import java.util.*; import org.eclipse.persistence.mappings.DatabaseMapping; import org.eclipse.persistence.queries.*; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.expressions.*; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.queries.*; /** * This is used to support subselects. * The subselect represents a mostly independent (has own expression builder) query using a report query. * Subselects can be used for, in (single column), exists (empty or non-empty), comparisons (single value). */ public class SubSelectExpression extends BaseExpression { protected boolean hasBeenNormalized; protected ReportQuery subQuery; protected String attribute; protected Class returnType; protected Expression criteriaBase; public SubSelectExpression() { super(); subQuery = new ReportQuery(); } public SubSelectExpression(ReportQuery query, Expression baseExpression) { super(baseExpression); this.subQuery = query; } /** * INTERNAL: * Return if the expression is equal to the other. * This is used to allow dynamic expression's SQL to be cached. */ @Override public boolean equals(Object object) { if (this == object) { return true; } // Equality cannot easily be determined for sub-select expressions. return false; } /** * INTERNAL: * Used in debug printing of this node. */ @Override public String descriptionOfNodeType() { return "SubSelect"; } public ReportQuery getSubQuery() { initializeCountSubQuery(); return subQuery; } /** * INTERNAL: * This method creates a report query that counts the number of values in baseExpression.anyOf(attribute) * * For most queries, a ReportQuery will be created that does a simple count using an anonymous query. In the case of * a DirectCollectionMapping, the ReportQuery will use the baseExpression to create a join to the table * containing the Direct fields and count based on that join. */ protected void initializeCountSubQuery(){ if (criteriaBase != null && (subQuery.getItems() == null || subQuery.getItems().isEmpty())){ if (baseExpression.getSession() != null && ((ObjectExpression)baseExpression).getDescriptor() != null){ Class sourceClass = ((ObjectExpression)baseExpression).getDescriptor().getJavaClass(); ClassDescriptor descriptor = baseExpression.getSession().getDescriptor(sourceClass); if (descriptor != null){ DatabaseMapping mapping = descriptor.getMappingForAttributeName(attribute); if (mapping != null && mapping.isDirectCollectionMapping()){ subQuery.setExpressionBuilder(baseExpression.getBuilder()); subQuery.setReferenceClass(sourceClass); subQuery.addCount(attribute, subQuery.getExpressionBuilder().anyOf(attribute), returnType); return; } } } // Use an anonymous subquery that will get its reference class // set during SubSelectExpression.normalize. subQuery.addCount("COUNT", subQuery.getExpressionBuilder(), returnType); if (attribute != null){ subQuery.setSelectionCriteria(subQuery.getExpressionBuilder().equal(criteriaBase.anyOf(attribute))); } else { subQuery.setSelectionCriteria(subQuery.getExpressionBuilder().equal(criteriaBase)); } } } /** * INTERNAL: */ @Override public boolean isSubSelectExpression() { return true; } /** * INTERNAL: * For iterating using an inner class */ @Override public void iterateOn(ExpressionIterator iterator) { super.iterateOn(iterator); if (baseExpression != null) { baseExpression.iterateOn(iterator); } // For Flashback: It is now possible to create iterators that will span // the entire expression, even the where clause embedded in a subQuery. if (iterator.shouldIterateOverSubSelects()) { if (getSubQuery().getSelectionCriteria() != null) { getSubQuery().getSelectionCriteria().iterateOn(iterator); } else { getSubQuery().getExpressionBuilder().iterateOn(iterator); } } } /** * INTERNAL: * The subquery must be normalized with the knowledge of the outer statement for outer references and correct aliasing. * For CR#4223 it will now be normalized after the outer statement is, rather than * somewhere in the middle of the outer statement's normalize. */ @Override public Expression normalize(ExpressionNormalizer normalizer) { if (this.hasBeenNormalized) { return this; } //has no effect but validateNode is here for consistency validateNode(); // Defer normalization of this expression until later. normalizer.addSubSelectExpression(this); normalizer.getStatement().setRequiresAliases(true); return this; } /** * INTERNAL: * Normalize this expression now that the parent statement has been normalized. * For CR#4223 */ public Expression normalizeSubSelect(ExpressionNormalizer normalizer, Map clonedExpressions) { if (this.hasBeenNormalized) { return this; } this.hasBeenNormalized = true; normalizer.getStatement().setRequiresAliases(true); // Anonymous subqueries: The following is to support sub-queries created // on the fly by OSQL Expressions isEmpty(), isNotEmpty(), size(). if (!getSubQuery().isCallQuery() && (getSubQuery().getReferenceClass() == null)) { ReportQuery subQuery = getSubQuery(); Expression criteria = subQuery.getSelectionCriteria(); // The criteria should be of form builder.equal(exp), where exp belongs // to the parent statement and has already been normalized, hence it // knows its reference class. if (criteria instanceof LogicalExpression) { criteria = ((LogicalExpression)criteria).getFirstChild(); } if (criteria instanceof RelationExpression) { Expression rightChild = ((RelationExpression)criteria).getSecondChild(); if (rightChild instanceof QueryKeyExpression) { ClassDescriptor descriptor = ((QueryKeyExpression)rightChild).getDescriptor(); // descriptor will be null here for query key expressions if (descriptor ==null){ descriptor = ((ObjectExpression)((QueryKeyExpression)rightChild).getBaseExpression()).getDescriptor(); } subQuery.setReferenceClass(descriptor.getJavaClass()); } } } //has no effect but validateNode is here for consistency validateNode(); getSubQuery().prepareSubSelect(normalizer.getSession(), null, clonedExpressions); if (!getSubQuery().isCallQuery()) { SQLSelectStatement statement = (SQLSelectStatement)((StatementQueryMechanism)getSubQuery().getQueryMechanism()).getSQLStatement(); // setRequiresAliases was already set for parent statement. statement.setRequiresAliases(true); statement.setParentStatement(normalizer.getStatement()); statement.normalize(normalizer.getSession(), getSubQuery().getDescriptor(), clonedExpressions); } return this; } /** * The query must be cloned, and the sub-expression must be cloned using the same outer expression identity. */ @Override protected void postCopyIn(Map alreadyDone) { initializeCountSubQuery(); super.postCopyIn(alreadyDone); ReportQuery clonedQuery = (ReportQuery)getSubQuery().clone(); if (!clonedQuery.isCallQuery()) { if (clonedQuery.getSelectionCriteria() != null) { clonedQuery.setSelectionCriteria(getSubQuery().getSelectionCriteria().copiedVersionFrom(alreadyDone)); // ensure the builder for the subquery is the same as the builder for the subquery's expression // for certain Subqueries (for instance batch queries for direct collections), when we get to this // point the builder for the clonedQuery will already be aliased. Replacing the builder with // the builder for the query's new expression solves this issue. if (clonedQuery.getExpressionBuilder() != null) { clonedQuery.setExpressionBuilder((ExpressionBuilder)clonedQuery.getExpressionBuilder().copiedVersionFrom(alreadyDone)); } } else if (clonedQuery.getExpressionBuilder() != null) { // Must clone the expression builder. clonedQuery.setExpressionBuilder((ExpressionBuilder)clonedQuery.getExpressionBuilder().copiedVersionFrom(alreadyDone)); } // Must also clone report items, group by, having, order by. clonedQuery.copyReportItems(alreadyDone); } setSubQuery(clonedQuery); } /** * Print the sub query to the printer. */ protected void printCustomSQL(ExpressionSQLPrinter printer) { /* * modified for bug#2658466. This fix ensures that Custom SQL sub-queries are translated * and have variables substituted with values correctly. */ SQLCall call = (SQLCall)getSubQuery().getCall(); call.translateCustomQuery(); printer.getCall().getParameters().addAll(call.getParameters()); printer.getCall().getParameterTypes().addAll(call.getParameterTypes()); printer.printString(call.getCallString()); } /** * Print the sub query to the printer. */ @Override public void printSQL(ExpressionSQLPrinter printer) { ReportQuery query = getSubQuery(); printer.printString("("); if (query.isCallQuery()) { printCustomSQL(printer); } else { SQLSelectStatement statement = (SQLSelectStatement)((ExpressionQueryMechanism)query.getQueryMechanism()).getSQLStatement(); boolean isFirstElementPrinted = printer.isFirstElementPrinted(); printer.setIsFirstElementPrinted(false); boolean requiresDistinct = printer.requiresDistinct(); statement.printSQL(printer); printer.setIsFirstElementPrinted(isFirstElementPrinted); printer.setRequiresDistinct(requiresDistinct); } printer.printString(")"); } /** * Should not rebuild as has its on expression builder. */ @Override public Expression rebuildOn(Expression newBase) { // TODO: This should be using rebuildOn not twist, but needs to pass the oldBase to rebuildOn // and only the oldBase should be replaced with the new base. // Further the twist, rebuildOn, and copy are doing essentially the same thing // and should be merged into a single method instead of having three methods doing the same thing 3 different ways // and all having various different bugs in them. SubSelectExpression subSelect = (SubSelectExpression) shallowClone(); // Rebuild base expression subSelect.setBaseExpression(getBaseExpression().rebuildOn(newBase)); // Clone report query ReportQuery reportQuery = (ReportQuery) getSubQuery().clone(); // Twist report items List<ReportItem> newItems = new ArrayList<ReportItem>(getSubQuery().getItems().size()); for (ReportItem item : reportQuery.getItems()) { newItems.add(new ReportItem(item.getName(), item.getAttributeExpression().twistedForBaseAndContext(newBase, getBuilder(), getBaseExpression()))); } reportQuery.setItems(newItems); // Twist group by expressions if (reportQuery.hasGroupByExpressions()) { List<Expression> groupByExpressions = new ArrayList<Expression>(reportQuery.getGroupByExpressions().size()); for (Expression groupByExpression : reportQuery.getGroupByExpressions()) { groupByExpressions.add(groupByExpression.twistedForBaseAndContext(newBase, getBuilder(), getBaseExpression())); } reportQuery.setGroupByExpressions(groupByExpressions); } // Twist order by expressions if (reportQuery.hasOrderByExpressions()) { List<Expression> orderByExpressions = new ArrayList<Expression>(reportQuery.getOrderByExpressions().size()); for (Expression orderByExpression : reportQuery.getOrderByExpressions()) { orderByExpressions.add(orderByExpression.twistedForBaseAndContext(newBase, getBuilder(), getBaseExpression())); } reportQuery.setOrderByExpressions(orderByExpressions); } // Twist union expressions if (reportQuery.hasUnionExpressions()) { List<Expression> unionExpressions = new ArrayList<Expression>(reportQuery.getUnionExpressions().size()); for (Expression unionExpression : reportQuery.getUnionExpressions()) { unionExpressions.add(unionExpression.twistedForBaseAndContext(newBase, getBuilder(), getBaseExpression())); } reportQuery.setUnionExpressions(unionExpressions); } // Twist selection criteria if (reportQuery.getSelectionCriteria() != null) { reportQuery.setSelectionCriteria(reportQuery.getSelectionCriteria().twistedForBaseAndContext(newBase, getBuilder(), getBaseExpression())); } // Set the cloned report query subSelect.setSubQuery(reportQuery); return subSelect; } /** * INTERNAL: * Search the tree for any expressions (like SubSelectExpressions) that have been * built using a builder that is not attached to the query. This happens in case of an Exists * call using a new ExpressionBuilder(). This builder needs to be replaced with one from the query. */ @Override public void resetPlaceHolderBuilder(ExpressionBuilder queryBuilder){ if(this.baseExpression.isExpressionBuilder() && ((ExpressionBuilder)this.baseExpression).wasQueryClassSetInternally()){ this.baseExpression = queryBuilder; if (this.builder != null){ this.builder = queryBuilder; } }else{ this.baseExpression.resetPlaceHolderBuilder(queryBuilder); } } public void setSubQuery(ReportQuery subQuery) { this.subQuery = subQuery; } @Override public Expression twistedForBaseAndContext(Expression newBase, Expression context, Expression oldBase) { SubSelectExpression subSelect = (SubSelectExpression) shallowClone(); // Twist base expression subSelect.setBaseExpression(subSelect.getBaseExpression().twistedForBaseAndContext(newBase, context, oldBase)); // Clone report query ReportQuery reportQuery = (ReportQuery) getSubQuery().clone(); // Twist report items List<ReportItem> newItems = new ArrayList<ReportItem>(getSubQuery().getItems().size()); for (ReportItem item : getSubQuery().getItems()) { newItems.add(new ReportItem(item.getName(), item.getAttributeExpression().twistedForBaseAndContext(newBase, context, getBaseExpression()))); } reportQuery.setItems(newItems); // Twist group by expressions if (getSubQuery().hasGroupByExpressions()) { List<Expression> groupByExpressions = new ArrayList<Expression>(getSubQuery().getGroupByExpressions().size()); for (Expression groupByExpression : getSubQuery().getGroupByExpressions()) { groupByExpressions.add(groupByExpression.twistedForBaseAndContext(newBase, context, getBaseExpression())); } reportQuery.setGroupByExpressions(groupByExpressions); } // Twist order by expressions if (getSubQuery().hasOrderByExpressions()) { List<Expression> orderByExpressions = new ArrayList<Expression>(getSubQuery().getOrderByExpressions().size()); for (Expression orderByExpression : getSubQuery().getOrderByExpressions()) { orderByExpressions.add(orderByExpression.twistedForBaseAndContext(newBase, context, getBaseExpression())); } reportQuery.setOrderByExpressions(orderByExpressions); } // Twist union expressions if (getSubQuery().hasUnionExpressions()) { List<Expression> unionByExpressions = new ArrayList<Expression>(getSubQuery().getUnionExpressions().size()); for (Expression unionExpression : getSubQuery().getUnionExpressions()) { unionByExpressions.add(unionExpression.twistedForBaseAndContext(newBase, context, getBaseExpression())); } reportQuery.setUnionExpressions(unionByExpressions); } // Twist selection criteria if (getSubQuery().getSelectionCriteria() != null) { reportQuery.setSelectionCriteria(getSubQuery().getSelectionCriteria().twistedForBaseAndContext(newBase, context, getBaseExpression())); } // Set the clones report query subSelect.setSubQuery(reportQuery); return subSelect; } /** * INTERNAL: * Used to print a debug form of the expression tree. */ @Override public void writeDescriptionOn(BufferedWriter writer) throws IOException { writer.write(String.valueOf(getSubQuery())); } /** * INTERNAL: * Used in SQL printing. */ @Override public void writeSubexpressionsTo(BufferedWriter writer, int indent) throws IOException { if (getSubQuery().getSelectionCriteria() != null) { getSubQuery().getSelectionCriteria().toString(writer, indent); } } /** * INTERNAL: called from SQLSelectStatement.writeFieldsFromExpression(...) * This allows a sub query in the select clause. */ @Override public void writeFields(ExpressionSQLPrinter printer, Vector newFields, SQLSelectStatement statement) { //print ", " before each selected field except the first one if (printer.isFirstElementPrinted()) { printer.printString(", "); } else { printer.setIsFirstElementPrinted(true); } // This field is complex so any name can be used. DatabaseField field = new DatabaseField("*"); field.setSqlType(DatabaseField.NULL_SQL_TYPE); newFields.add(field); printSQL(printer); } /** * INTERNAL: * This factory method is used to build a subselect that will do a count. * It will count the number of items in baseExpression.anyOf(attribute). */ public static SubSelectExpression createSubSelectExpressionForCount(Expression outerQueryBaseExpression, Expression outerQueryCriteria, String attribute, Class returnType){ SubSelectExpression expression = new SubSelectExpression(); expression.setBaseExpression(outerQueryBaseExpression); expression.attribute = attribute; expression.criteriaBase = outerQueryCriteria; if (returnType != null){ expression.returnType = returnType; } return expression; } }