org.eclipse.persistence.internal.jpa.querydef.CriteriaQueryImpl Maven / Gradle / Ivy
Show all versions of eclipselink Show documentation
/*
* Copyright (c) 2011, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2024 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:
// Gordon Yorke - Initial development
// 10/01/2018: Will Dazey
// - #253: Add support for embedded constructor results with CriteriaBuilder
// 10/25/2023: Tomas Kraus
// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.internal.jpa.querydef;
import java.lang.reflect.Constructor;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Selection;
import jakarta.persistence.metamodel.Metamodel;
import jakarta.persistence.metamodel.Type.PersistenceType;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.internal.helper.BasicTypeHelperImpl;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl;
import org.eclipse.persistence.internal.jpa.metamodel.TypeImpl;
import org.eclipse.persistence.internal.localization.ExceptionLocalization;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedGetConstructorFor;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.ReadAllQuery;
import org.eclipse.persistence.queries.ReportQuery;
/**
*
* Purpose: Contains the implementation of the CriteriaQuery interface of
* the JPA criteria API.
*
* Description: This is the container class for the components that
* define a query.
*
* @see jakarta.persistence.criteria CriteriaQuery
*
* @author gyorke
* @since EclipseLink 1.2
*/
public class CriteriaQueryImpl extends AbstractQueryImpl implements CriteriaQuery {
protected SelectionImpl extends T> selection;
protected List orderBy;
protected Set joins;
// Mark this query as part of the UNION/EXCEPT/INTERSECT, default is false
private boolean isUnion = false;
public CriteriaQueryImpl(Metamodel metamodel, ResultType queryResult, Class result, CriteriaBuilderImpl queryBuilder) {
super(metamodel, queryResult, queryBuilder, result);
}
@Override
public CriteriaQuery select(Selection extends T> selection) {
this.selection = (SelectionImpl extends T>) selection;
this.selection.findRootAndParameters(this);
if (selection.isCompoundSelection()) {
//bug 366386: validate that aliases are not reused
if (this.selection.isCompoundSelection() && ((CompoundSelectionImpl)this.selection).getDuplicateAliasNames() != null) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("jpa_criteriaapi_alias_reused",
new Object[] { ((CompoundSelectionImpl)this.selection).getDuplicateAliasNames() }));
}
if (selection.getJavaType().equals(Tuple.class)) {
this.queryResult = ResultType.TUPLE;
this.queryType = (Class) Tuple.class;
} else if (((InternalSelection) selection).isConstructor()) {
Selection[] selectArray = selection.getCompoundSelectionItems().toArray(new Selection[0]);
populateAndSetConstructorSelection((ConstructorSelectionImpl)selection, (Class) this.selection.getJavaType(), selectArray);
this.queryType = (Class) selection.getJavaType();
} else {
this.queryResult = ResultType.OBJECT_ARRAY;
this.queryType = (Class) ClassConstants.AOBJECT;
}
} else {
// Update query type only when it's not null in selection argument.
Class> queryType = selection.getJavaType();
if (queryType != null) {
this.queryType = (Class) queryType;
}
TypeImpl type = ((MetamodelImpl)this.metamodel).getType(this.queryType);
if (type != null && type.getPersistenceType().equals(PersistenceType.ENTITY)) {
this.queryResult = ResultType.ENTITY; // this will be a selection item in a report query
} else {
this.queryResult = ResultType.OTHER;
}
}
return this;
}
/**
* Specify the items that are to be returned in the query result. Replaces
* the previously specified selection(s), if any.
*
* The type of the result of the query execution depends on the
* specification of the criteria query object as well as the arguments to
* the multiselect method as follows:
*
* If the type of the criteria query is CriteriaQuery<Tuple>, a Tuple object
* corresponding to the arguments of the multiselect method will be
* instantiated and returned for each row that results from the query
* execution.
*
* If the type of the criteria query is CriteriaQuery<X> for some
* user-defined class X, then the arguments to the multiselect method will
* be passed to the X constructor and an instance of type X will be returned
* for each row. The IllegalStateException will be thrown if a constructor
* for the given argument types does not exist.
*
* If the type of the criteria query is CriteriaQuery<X[]> for some class X,
* an instance of type X[] will be returned for each row. The elements of
* the array will correspond to the arguments of the multiselect method. The
* IllegalStateException will be thrown if the arguments to the multiselect
* method are not of type X.
*
* If the type of the criteria query is CriteriaQuery<Object>, and only a
* single argument is passed to the multiselect method, an instance of type
* Object will be returned for each row.
*
* If the type of the criteria query is CriteriaQuery<Object>, and more than
* one argument is passed to the multiselect method, an instance of type
* Object[] will be instantiated and returned for each row. The elements of
* the array will correspond to the arguments to the multiselect method.
*
* @param selections
* expressions specifying the items that are to be returned in
* the query result
* @return the modified query
*/
@Override
public CriteriaQuery multiselect(Selection>... selections) {
if (selections == null || selections.length == 0) {
this.selection = null;
return this;
}
for (Selection select : selections) {
((SelectionImpl)select).findRootAndParameters(this);
}
if (this.queryResult == ResultType.CONSTRUCTOR) {
populateAndSetConstructorSelection(null, this.queryType, selections);
} else if (this.queryResult.equals(ResultType.ENTITY)) {
if (selections.length == 1 && selections[0].getJavaType().equals(this.queryType)) {
this.selection = (SelectionImpl extends T>) selections[0];
} else {
try {
populateAndSetConstructorSelection(null, this.queryType, selections);//throws IllegalArgumentException if it doesn't exist
} catch(IllegalArgumentException constructorDoesNotExist){
this.queryResult = ResultType.PARTIAL;
this.selection = new CompoundSelectionImpl(this.queryType, selections);
}
}
} else if (this.queryResult.equals(ResultType.TUPLE)) {
this.selection = new CompoundSelectionImpl(this.queryType, selections);
} else if (this.queryResult.equals(ResultType.OTHER)) {
if (selections.length == 1 && selections[0].getJavaType().equals(this.queryType)) {
this.selection = (SelectionImpl extends T>) selections[0];
} else {
if (!BasicTypeHelperImpl.getInstance().isDateClass(this.queryType)) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("MULTIPLE_SELECTIONS_PASSED_TO_QUERY_WITH_PRIMITIVE_RESULT"));
}
populateAndSetConstructorSelection(null, this.queryType, selections);
}
} else { // unknown
this.selection = new CompoundSelectionImpl(this.queryType, selections);
}
//bug 366386: validate that aliases are not reused
if (this.selection.isCompoundSelection() && ((CompoundSelectionImpl)this.selection).getDuplicateAliasNames() != null) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("jpa_criteriaapi_alias_reused",
new Object[] { ((CompoundSelectionImpl)this.selection).getDuplicateAliasNames() }));
}
return this;
}
/**
* Specify the items that are to be returned in the query result. Replaces
* the previously specified selection(s), if any.
*
* The type of the result of the query execution depends on the
* specification of the criteria query object as well as the arguments to
* the multiselect method as follows:
*
* If the type of the criteria query is CriteriaQuery<Tuple>, a Tuple object
* corresponding to the items in the selection list passed to the
* multiselect method will be instantiated and returned for each row that
* results from the query execution.
*
* If the type of the criteria query is CriteriaQuery<X> for some
* user-defined class X, then the items in the selection list passed to the
* multiselect method will be passed to the X constructor and an instance of
* type X will be returned for each row. The IllegalStateException will be
* thrown if a constructor for the given argument types does not exist.
*
* If the type of the criteria query is CriteriaQuery<X[]> for some class X,
* an instance of type X[] will be returned for each row. The elements of
* the array will correspond to the items in the selection list passed to
* the multiselect method. The IllegalStateException will be thrown if the
* elements in the selection list passed to the multiselect method are not
* of type X.
*
* If the type of the criteria query is CriteriaQuery<Object>, and the
* selection list passed to the multiselect method contains only a single
* item, an instance of type Object will be returned for each row.
*
* If the type of the criteria query is CriteriaQuery<Object>, and the
* selection list passed to the multiselect method contains more than one
* item, an instance of type Object[] will be instantiated and returned for
* each row. The elements of the array will correspond to the items in the
* selection list passed to the multiselect method.
*
* @param selectionList
* list of expressions specifying the items that to be are
* returned in the query result
* @return the modified query
*/
@Override
public CriteriaQuery multiselect(List> selectionList) {
if (selectionList == null) {
this.selection = null;
return this;
}
return this.multiselect(selectionList.toArray(new Selection[0]));
}
// override the return type only:
@Override
public CriteriaQuery where(Expression restriction) {
return (CriteriaQuery) super.where(restriction);
}
@Override
public CriteriaQuery where(Predicate... restrictions) {
return (CriteriaQuery) super.where(restrictions);
}
@Override
public CriteriaQuery where(List restrictions) {
return (CriteriaQuery) super.where(restrictions);
}
/**
* Specify the expressions that are used to form groups over the query
* results. Replaces the previous specified grouping expressions, if any. If
* no grouping expressions are specified, any previously added grouping
* expressions are simply removed. This method only overrides the return
* type of the corresponding AbstractQuery method.
*
* @param grouping
* zero or more grouping expressions
* @return the modified query
*/
@Override
public CriteriaQuery groupBy(Expression>... grouping) {
super.groupBy(grouping);
return this;
}
/**
* Specify the expressions that are used to form groups over the query
* results. Replaces the previous specified grouping expressions, if any. If
* no grouping expressions are specified, any previously added grouping
* expressions are simply removed. This method only overrides the return
* type of the corresponding AbstractQuery method.
*
* @param grouping
* list of zero or more grouping expressions
* @return the modified query
*/
@Override
public CriteriaQuery groupBy(List> grouping) {
super.groupBy(grouping);
return this;
}
@Override
public CriteriaQuery having(Expression restriction) {
super.having(restriction);
return this;
}
@Override
public CriteriaQuery having(Predicate... restrictions) {
super.having(restrictions);
return this;
}
@Override
public CriteriaQuery having(List restrictions) {
super.having(restrictions);
return this;
}
/**
* Specify the ordering expressions that are used to order the query
* results. Replaces the previous ordering expressions, if any. If no
* ordering expressions are specified, the previous ordering, if any, is
* simply removed, and results will be returned in no particular order. The
* left-to-right sequence of the ordering expressions determines the
* precedence, whereby the leftmost has highest precedence.
*
* @param o
* zero or more ordering expressions
* @return the modified query.
*/
@Override
public CriteriaQuery orderBy(Order... o) {
this.orderBy = new ArrayList<>();
for (Order order : o) {
findRootAndParameters(order);
this.orderBy.add(order);
}
return this;
}
/**
* Specify the ordering expressions that are used to order the query
* results. Replaces the previous ordering expressions, if any. If no
* ordering expressions are specified, the previous ordering, if any, is
* simply removed, and results will be returned in no particular order. The
* order of the ordering expressions in the list determines the precedence,
* whereby the first element in the list has highest precedence.
*
* @param o
* list of zero or more ordering expressions
* @return the modified query.
*/
@Override
public CriteriaQuery orderBy(List o) {
for (Order order : o) {
findRootAndParameters(order);
}
this.orderBy = o;
return this;
}
/**
* This method will set this queryImpl's selection to a ConstructorSelectionImpl, creating a new
* instance or populating the one passed in as necessary.
* Throws IllegalArgumentException if a constructor taking arguments represented
* by the selections array doesn't exist for the given class.
*
* Also sets the query result to ResultType.CONSTRUCTOR
*
*/
public void populateAndSetConstructorSelection(ConstructorSelectionImpl constructorSelection, Class class1, Selection>... selections) throws IllegalArgumentException{
Class>[] constructorArgs = new Class>[selections.length];
int count = 0;
for (Selection> select : selections) {
if(select instanceof ConstructorSelectionImpl) {
@SuppressWarnings("unchecked")
ConstructorSelectionImpl constructorSelect = (ConstructorSelectionImpl)select;
@SuppressWarnings("unchecked")
Selection extends T>[] selectArray = constructorSelect.getCompoundSelectionItems().toArray(new Selection[0]);
populateAndSetConstructorSelection(constructorSelect, (Class) constructorSelect.getJavaType(), selectArray);
}
constructorArgs[count++] = select.getJavaType();
}
Constructor extends T> constructor = null;
try {
// TODO: Remove AccessController.doPrivileged
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
constructor = AccessController.doPrivileged(new PrivilegedGetConstructorFor<>(class1, constructorArgs, false));
} else {
constructor = PrivilegedAccessHelper.getConstructorFor(class1, constructorArgs, false);
}
if (constructorSelection == null){
constructorSelection = new ConstructorSelectionImpl<>(class1, selections);
}
this.queryResult = ResultType.CONSTRUCTOR;
constructorSelection.setConstructor(constructor);
constructorSelection.setConstructorArgTypes(constructorArgs);
this.selection = constructorSelection;
} catch (Exception e){
//PrivilegedActionException and NoSuchMethodException are possible
Object[] params = new Object[1];
params[0] = this.queryType;
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("criteria_no_constructor_found", params), e);
}
}
/**
* Specify whether duplicate query results will be eliminated. A true value
* will cause duplicates to be eliminated. A false value will cause
* duplicates to be retained. If distinct has not been specified, duplicate
* results must be retained. This method only overrides the return type of
* the corresponding AbstractQuery method.
*
* @param distinct
* boolean value specifying whether duplicate results must be
* eliminated from the query result or whether they must be
* retained
* @return the modified query.
*/
@Override
public CriteriaQuery distinct(boolean distinct) {
super.distinct(distinct);
return this;
}
@Override
public void addJoin(FromImpl from) {
if (this.joins == null) {
this.joins = new LinkedHashSet<>();
}
this.joins.add(from);
}
/**
* Create {@link ReadAllQuery} or {@link ReportQuery} from content of this instance.
* May be forced to always return {@link ReportQuery}, even when {@link ReadAllQuery} is sufficient.
* This is required for complex queries with {@code UNION}/{@code EXCEPT}/{@code INTERSECT}, where
* only {@link ReportQuery} instance is accepted as part of the query.
*
* @param toReportQuery force {@link ReportQuery} creation.
* @return {@link ReadAllQuery} or {@link ReportQuery} from content of this instance
*/
protected ReadAllQuery getDatabaseQuery(boolean toReportQuery) {
ReadAllQuery query = null;
if (this.selection == null || !this.selection.isCompoundSelection()) {
query = createSimpleQuery(toReportQuery);
} else {
query = createCompoundQuery(toReportQuery);
}
return query;
}
/**
* Create {@link ReadAllQuery} or {@link ReportQuery} from content of this instance.
* Returned class depends on current query complexity.
*
* @return {@link ReadAllQuery} or {@link ReportQuery} from content of this instance
*/
@Override
protected DatabaseQuery getDatabaseQuery() {
return getDatabaseQuery(false);
}
/**
* Return the ordering expressions in order of precedence.
*
* @return the list of ordering expressions
*/
@Override
public List getOrderList() {
return this.orderBy;
}
/**
* Return the selection item of the query. This will correspond to the query
* type.
*
* @return the selection item of the query
*/
@Override
public Selection getSelection() {
return (Selection) this.selection;
}
/**
* Translates from the criteria query to a EclipseLink Database Query.
*/
@SuppressWarnings("deprecation")
protected ReadAllQuery createCompoundQuery(boolean toReportQuery) {
ReadAllQuery query = null;
if (this.queryResult == ResultType.UNKNOWN) {
if (this.selection.isConstructor()) {
this.queryResult = ResultType.CONSTRUCTOR;
} else if (this.selection.getJavaType().equals(Tuple.class)) {
this.queryResult = ResultType.TUPLE;
} else {
this.queryResult = ResultType.OBJECT_ARRAY;
}
}
if (this.queryResult.equals(ResultType.PARTIAL)) {
ReadAllQuery raq;
@SuppressWarnings("unchecked")
List> selectionItems = (List>) (List>) this.selection.getCompoundSelectionItems();
if (toReportQuery) {
raq = createReportQuery(this.queryType);
for (SelectionImpl> nested : selectionItems) {
((ReportQuery) raq).addAttribute(nested.getAlias(), nested.getCurrentNode(), nested.getJavaType());
}
} else {
raq = new ReadAllQuery(this.queryType);
}
// Partial object queries are not allowed to maintain the cache or be edited. Requires dontMaintainCache().
// See hasPartialAttributeExpressions() in ObjectLevelReadQuery#prepareQuery()
raq.dontMaintainCache();
for (SelectionImpl> selection : selectionItems) {
raq.addPartialAttribute((selection).currentNode);
}
raq.setExpressionBuilder(((InternalSelection) this.selection.getCompoundSelectionItems().get(0)).getCurrentNode().getBuilder());
query = raq;
} else {
ReportQuery reportQuery = null;
if (this.queryResult.equals(ResultType.CONSTRUCTOR) || this.queryResult.equals(ResultType.OTHER)) {
// other is also a constructor type if multi-select was called.
// with a type other than the query type.
reportQuery = new ReportQuery();
reportQuery.addConstructorReportItem(((ConstructorSelectionImpl) this.selection).translate());
reportQuery.setShouldReturnSingleAttribute(true);
} else {
if (this.queryResult.equals(ResultType.TUPLE)) {
reportQuery = new TupleQuery(this.selection == null ? new ArrayList<>() : this.selection.getCompoundSelectionItems());
} else {
reportQuery = new ReportQuery();
reportQuery.setShouldReturnWithoutReportQueryResult(true);
}
reportQuery.setExpressionBuilder(((InternalSelection) this.selection.getCompoundSelectionItems().get(0)).getCurrentNode().getBuilder());
for (Selection nested : this.selection.getCompoundSelectionItems()) {
if (((SelectionImpl) nested).isConstructor()) {
reportQuery.addConstructorReportItem(((ConstructorSelectionImpl) nested).translate());
} else if (nested.isCompoundSelection()) {
throw new IllegalStateException(ExceptionLocalization.buildMessage("NESTED_COMPOUND_SELECTION_OTHER_THAN_CONSTRUCTOR_NOT_SUPPORTED"));
} else {
if (((InternalSelection) nested).isFrom()) {
reportQuery.addItem(nested.getAlias(), ((SelectionImpl) nested).getCurrentNode(), ((FromImpl) nested).findJoinFetches());
} else if (((InternalExpression) nested).isCompoundExpression() && ((FunctionExpressionImpl) nested).getOperation() == CriteriaBuilderImpl.SIZE) {
//selecting size not all databases support subselect in select clause so convert to count/groupby
PathImpl collectionExpression = (PathImpl) ((FunctionExpressionImpl) nested).getChildExpressions().get(0);
ExpressionImpl fromExpression = (ExpressionImpl) collectionExpression.getParentPath();
reportQuery.addAttribute(nested.getAlias(), collectionExpression.getCurrentNode().count(), ClassConstants.INTEGER);
reportQuery.addGrouping(fromExpression.getCurrentNode());
} else {
reportQuery.addAttribute(nested.getAlias(), ((SelectionImpl) nested).getCurrentNode(), nested.getJavaType());
}
}
}
}
ExpressionBuilder builder = null;
Class> queryClazz = null;
// First check the WHERE clause
if(this.where != null && ((InternalSelection) this.where).getCurrentNode() != null) {
builder = ((InternalSelection) this.where).getCurrentNode().getBuilder();
queryClazz = builder.getQueryClass();
}
// Check all the SELECTION items next
if(queryClazz == null && this.selection != null) {
for(Selection> s : this.selection.getCompoundSelectionItems()) {
if(((InternalSelection) s).getCurrentNode() != null ) {
builder = ((InternalSelection) s).getCurrentNode().getBuilder();
queryClazz = builder.getQueryClass();
if(queryClazz != null) {
break;
}
}
}
}
// Fallback on the root
if(queryClazz == null && this.roots != null) {
for(Root> r : this.roots) {
if(((RootImpl>) r).getCurrentNode() != null ) {
builder = ((RootImpl>) r).getCurrentNode().getBuilder();
queryClazz = builder.getQueryClass();
if(queryClazz != null) {
break;
}
}
}
}
reportQuery.setExpressionBuilder(builder);
reportQuery.setReferenceClass(queryClazz);
query = reportQuery;
if (this.groupBy != null && !this.groupBy.isEmpty()) {
for (Expression> exp : this.groupBy) {
reportQuery.addGrouping(((InternalSelection) exp).getCurrentNode());
}
}
if (this.havingClause != null) {
reportQuery.setHavingExpression(((InternalSelection) this.havingClause).getCurrentNode());
}
}
return query;
}
protected ReadAllQuery createSimpleQuery(boolean toReportQuery) {
ReadAllQuery query = null;
if (this.queryResult == ResultType.UNKNOWN) {
// unknown type so let's figure this out.
if (selection == null) {
if (this.roots != null && !this.roots.isEmpty()) {
this.selection = (SelectionImpl extends T>) this.roots.iterator().next();
if (toReportQuery) {
// TODO: Test and fix
query = createReportQueryWithItem(((FromImpl) this.selection).getJavaType());
} else {
query = new ReadAllQuery(((FromImpl) this.selection).getJavaType());
}
List list = ((FromImpl) this.roots.iterator().next()).findJoinFetches();
for (org.eclipse.persistence.expressions.Expression fetch : list) {
query.addJoinedAttribute(fetch);
}
if (!list.isEmpty()) {
query.setExpressionBuilder(list.get(0).getBuilder());
}
} else if (this.roots == null || this.roots.isEmpty()) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("CRITERIA_NO_ROOT_FOR_COMPOUND_QUERY"));
}
} else {
// Selection is not null set type to selection
TypeImpl extends T> type = ((MetamodelImpl)this.metamodel).getType(selection.getJavaType());
if (type != null && type.getPersistenceType().equals(PersistenceType.ENTITY)) {
if (toReportQuery) {
query = createReportQueryWithItem(type.getJavaType());
((ReportQuery) query).setShouldReturnWithoutReportQueryResult(true);
} else {
query = new ReadAllQuery(type.getJavaType());
}
List list = ((FromImpl, ?>) this.roots.iterator().next()).findJoinFetches();
for (org.eclipse.persistence.expressions.Expression fetch : list) {
query.addJoinedAttribute(fetch);
}
query.setExpressionBuilder(((InternalSelection)selection).getCurrentNode().getBuilder());
} else {
query = createReportQueryWithSelection(this.selection.getCurrentNode().getBuilder().getQueryClass());
}
}
} else if (this.queryResult.equals(ResultType.ENTITY)) {
boolean nonRootSelection = this.selection != null && (!((InternalSelection) this.selection).isRoot());
if (nonRootSelection) {
query = createReportQueryWithItem(this.queryType);
((ReportQuery) query).setShouldReturnSingleAttribute(true);
} else {
// Tested by testUnionAllWithNoSelection
if (toReportQuery) {
query = createReportQueryWithItem(this.queryType);
((ReportQuery) query).setShouldReturnWithoutReportQueryResult(true);
} else {
query = new ReadAllQuery(this.queryType);
}
}
// Union query always needs proper ExpressionBuilder set
if (!nonRootSelection || isUnion) {
boolean doSetExpressionBuilder = true;
if (this.roots != null && !this.roots.isEmpty()) {
List list = ((FromImpl, ?>) this.roots.iterator().next()).findJoinFetches();
// Expression builder from root selection
if (!list.isEmpty()) {
// Set the builder to one of the fetches bases
query.setExpressionBuilder(list.get(0).getBuilder());
doSetExpressionBuilder = false;
}
if (this.selection == null || ((InternalSelection) this.selection).isRoot()) {
for (org.eclipse.persistence.expressions.Expression fetch : list) {
query.addJoinedAttribute(fetch);
}
}
}
// Set the builder to non-root selection as a fallback
if (doSetExpressionBuilder && selection != null) {
query.setExpressionBuilder(this.selection.currentNode.getBuilder());
}
}
} else {
ReportQuery reportQuery = null;
if (this.queryResult.equals(ResultType.TUPLE)) {
List