| /* |
| * 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 |
| package org.eclipse.persistence.internal.expressions; |
| |
| import java.io.BufferedWriter; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.expressions.ExpressionBuilder; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.queries.ExpressionQueryMechanism; |
| import org.eclipse.persistence.internal.queries.ReportItem; |
| import org.eclipse.persistence.internal.queries.StatementQueryMechanism; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.queries.ReportQuery; |
| import org.eclipse.persistence.queries.SQLCall; |
| |
| /** |
| * 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<>(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<>(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<>(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<>(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<>(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<>(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<>(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<>(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, List<DatabaseField> 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; |
| } |
| } |