org.eclipse.persistence.internal.expressions.SubSelectExpression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022 IBM Corporation. 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.*;
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.getCall().getParameterBindings().addAll(call.getParameterBindings());
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 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 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 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 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 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 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 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 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, 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;
}
}