org.eclipse.persistence.internal.jpa.querydef.SubQueryImpl Maven / Gradle / Ivy
Show all versions of eclipselink Show documentation
/*
* Copyright (c) 2011, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 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
// 02/03/2017 - Dalia Abo Sheasha
// - 509693 : EclipseLink generates inconsistent SQL statements for SubQuery
package org.eclipse.persistence.internal.jpa.querydef;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import jakarta.persistence.criteria.AbstractQuery;
import jakarta.persistence.criteria.CollectionJoin;
import jakarta.persistence.criteria.CommonAbstractCriteria;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.ListJoin;
import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.ParameterExpression;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Selection;
import jakarta.persistence.criteria.SetJoin;
import jakarta.persistence.criteria.Subquery;
import jakarta.persistence.metamodel.Metamodel;
import jakarta.persistence.metamodel.Type.PersistenceType;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.internal.expressions.ConstantExpression;
import org.eclipse.persistence.internal.expressions.SubSelectExpression;
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.queries.DatabaseQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.ReportQuery;
/**
*
* Purpose: Contains the implementation of the SubQuery interface of the JPA
* criteria API.
*
* Description: This is the container class for the components that define a query to
* be used in a sub select expression.
*
* @see jakarta.persistence.criteria CriteriaQuery
* @see jakarta.persistence.criteria SubQuery
*
* @author gyorke
* @since EclipseLink 2.0
*/
public class SubQueryImpl extends AbstractQueryImpl implements Subquery , InternalExpression, InternalSelection{
protected SelectionImpl> selection;
protected SubSelectExpression currentNode;
protected String alias;
protected ReportQuery subQuery;
protected Set> correlatedJoins;
protected CommonAbstractCriteria parent;
protected Set processedJoins;
protected Set correlations;
public SubQueryImpl(Metamodel metamodel, Class result, CriteriaBuilderImpl queryBuilder, CommonAbstractCriteria parent){
super(metamodel, ResultType.OTHER, queryBuilder, result);
this.subQuery = new ReportQuery();
TypeImpl queryType = ((MetamodelImpl)metamodel).getType(result);
if (queryType != null && queryType.getPersistenceType() == PersistenceType.ENTITY){
this.subQuery.setReferenceClass(result);
}
this.subQuery.setDistinctState(ObjectLevelReadQuery.DONT_USE_DISTINCT);
this.correlatedJoins = new HashSet<>();
this.correlations = new HashSet<>();
this.currentNode = new SubSelectExpression(subQuery, new ExpressionBuilder());
this.parent = parent;
}
// Allows complete copy of CommonAbstractCriteriaImpl. Required for cast implementation and shall remain private.
private SubQueryImpl(Metamodel metamodel, Expression where, CriteriaBuilderImpl queryBuilder,
Class queryType, Set> parameters,
ResultType queryResult, boolean distinct, Predicate havingClause,List> groupBy,
Set> roots, org.eclipse.persistence.expressions.Expression baseExpression,
SelectionImpl> selection,SubSelectExpression currentNode, String alias, ReportQuery subQuery,
Set> correlatedJoins, CommonAbstractCriteria parent, Set processedJoins,
Set correlations) {
super(metamodel, where, queryBuilder, queryType, parameters,
queryResult, distinct, havingClause, groupBy, roots, baseExpression);
this.selection = selection;
this.currentNode = currentNode;
this.alias = alias;
this.subQuery = subQuery;
this.correlatedJoins = correlatedJoins;
this.parent = parent;
this.processedJoins = processedJoins;
this.correlations = correlations;
}
/**
* Specify the item that is to be returned in the query result.
* Replaces the previously specified selection, if any.
* @param selection selection specifying the item that
* is to be returned in the query result
* @return the modified query
*/
@Override
public Subquery select(Expression selection) {
findRootAndParameters(selection);
for (Iterator> iterator = this.getRoots().iterator(); iterator.hasNext();){
findJoins((FromImpl)iterator.next());
}
for (Iterator> iterator = this.getCorrelatedJoins().iterator(); iterator.hasNext();){
findJoins((FromImpl)iterator.next());
}
this.selection = (SelectionImpl) selection;
this.queryType = (Class) selection.getJavaType();
this.subQuery.getItems().clear();
if (selection.isCompoundSelection()) {
int count = 0;
for (Selection select : selection.getCompoundSelectionItems()) {
this.subQuery.addItem(String.valueOf(count), ((InternalSelection) select).getCurrentNode());
++count;
}
this.subQuery.setExpressionBuilder(((InternalSelection)selection.getCompoundSelectionItems().get(0)).getCurrentNode().getBuilder());
} else {
TypeImpl extends T> type = ((MetamodelImpl)this.metamodel).getType(selection.getJavaType());
if (type != null && type.getPersistenceType().equals(PersistenceType.ENTITY)) {
this.subQuery.addAttribute("", new ConstantExpression(1, ((InternalSelection)selection).getCurrentNode().getBuilder()));
this.subQuery.addNonFetchJoinedAttribute(((InternalSelection)selection).getCurrentNode());
} else {
String itemName = selection.getAlias();
if (itemName == null){
itemName = ((InternalSelection) selection).getCurrentNode().getName();
}
this.subQuery.addItem(itemName, ((InternalSelection) selection).getCurrentNode());
}
this.subQuery.setExpressionBuilder(((InternalSelection)selection).getCurrentNode().getBuilder());
}
return this;
}
// override the return type only:
@Override
public Subquery where(Expression restriction){
super.where(restriction);
setWhereInternal();
return this;
}
@Override
public Subquery where(Predicate... restrictions) {
return where(restrictions != null ? List.of(restrictions) : null);
}
@Override
public Subquery where(List restrictions) {
super.where(restrictions);
setWhereInternal();
return this;
}
private void setWhereInternal() {
org.eclipse.persistence.expressions.Expression currentNode = ((InternalSelection)this.where).getCurrentNode();
for(org.eclipse.persistence.expressions.Expression exp: this.correlations){
currentNode = currentNode.and(exp);
}
this.subQuery.setSelectionCriteria(currentNode);
for (Iterator> iterator = this.getRoots().iterator(); iterator.hasNext();){
findJoins((FromImpl)iterator.next());
}
for (Iterator> iterator = this.getCorrelatedJoins().iterator(); iterator.hasNext();){
findJoins((FromImpl)iterator.next());
}
}
/**
* 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 Subquery groupBy(Expression>... grouping){
super.groupBy(grouping);
this.subQuery.getGroupByExpressions().clear();
for (Expression groupby: grouping){
this.subQuery.addGrouping(((InternalSelection)groupby).getCurrentNode());
}
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
* zero or more grouping expressions
* @return the modified query
*/
@Override
public Subquery groupBy(List> grouping){
super.groupBy(grouping);
this.subQuery.getGroupByExpressions().clear();
for (Expression groupby: grouping){
this.subQuery.addGrouping(((InternalSelection)groupby).getCurrentNode());
}
return this;
}
@Override
public Subquery having(Expression restriction){
super.having(restriction);
setHavingClauseInternal(((InternalSelection)restriction).getCurrentNode());
return this;
}
@Override
public Subquery having(Predicate... restrictions) {
return having(restrictions != null ? List.of(restrictions) : null);
}
@Override
public Subquery having(List restrictions) {
super.having(restrictions);
setHavingClauseInternal(((InternalSelection) this.havingClause).getCurrentNode());
return this;
}
private void setHavingClauseInternal(org.eclipse.persistence.expressions.Expression currentNode) {
if (this.havingClause != null) {
this.subQuery.setHavingExpression(currentNode);
} else {
this.subQuery.setHavingExpression(null);
}
}
/**
* Correlates a root of the enclosing query to a root of the subquery and
* returns the subquery root.
*
* @param parentRoot
* a root of the containing query
* @return subquery root
*/
@Override
public Root correlate(Root parentRoot){
RootImpl root = new RootImpl(parentRoot.getModel(), metamodel, parentRoot.getJavaType(), internalCorrelate((FromImpl)parentRoot), parentRoot.getModel(), (FromImpl) parentRoot);
integrateRoot(root);
return root;
}
/**
* Correlates a join object of the enclosing query to a join object of the
* subquery and returns the subquery join object.
*
* @param parentJoin
* join target of the containing query
* @return subquery join
*/
@Override
public Join correlate(Join parentJoin){
this.correlatedJoins.add(parentJoin);
JoinImpl join = new JoinImpl(parentJoin.getParentPath(), metamodel.managedType(parentJoin.getModel().getBindableJavaType()), metamodel, parentJoin.getJavaType(), internalCorrelate((FromImpl) parentJoin), parentJoin.getModel(), parentJoin.getJoinType(), (FromImpl) parentJoin);
return join;
}
/**
* Correlates a join to a Collection-valued association or element
* collection in the enclosing query to a join object of the subquery and
* returns the subquery join object.
*
* @param parentCollection
* join target of the containing query
* @return subquery join
*/
@Override
public CollectionJoin correlate(CollectionJoin parentCollection){
this.correlatedJoins.add(parentCollection);
return new CollectionJoinImpl(parentCollection.getParentPath(), metamodel.managedType(parentCollection.getModel().getBindableJavaType()), metamodel, parentCollection.getJavaType(), internalCorrelate((FromImpl) parentCollection), parentCollection.getModel(), parentCollection.getJoinType(), (FromImpl) parentCollection);
}
/**
* Correlates a join to a Set-valued association or element collection in
* the enclosing query to a join object of the subquery and returns the
* subquery join object.
*
* @param parentSet
* join target of the containing query
* @return subquery join
*/
@Override
public SetJoin correlate(SetJoin parentSet){
this.correlatedJoins.add(parentSet);
return new SetJoinImpl(parentSet.getParentPath(), metamodel.managedType(parentSet.getModel().getBindableJavaType()), metamodel, parentSet.getJavaType(), ((InternalSelection)parentSet).getCurrentNode(), parentSet.getModel(), parentSet.getJoinType(), (FromImpl) parentSet);
}
/**
* Correlates a join to a List-valued association or element collection in
* the enclosing query to a join object of the subquery and returns the
* subquery join object.
*
* @param parentList
* join target of the containing query
* @return subquery join
*/
@Override
public ListJoin correlate(ListJoin parentList){
this.correlatedJoins.add(parentList);
return new ListJoinImpl(parentList.getParentPath(), metamodel.managedType(parentList.getModel().getBindableJavaType()), metamodel, parentList.getJavaType(), internalCorrelate((FromImpl) parentList), parentList.getModel(), parentList.getJoinType(), (FromImpl) parentList);
}
/**
* Correlates a join to a Map-valued association or element collection in
* the enclosing query to a join object of the subquery and returns the
* subquery join object.
*
* @param parentMap
* join target of the containing query
* @return subquery join
*/
@Override
public MapJoin correlate(MapJoin parentMap){
this.correlatedJoins.add(parentMap);
return new MapJoinImpl(parentMap.getParentPath(), metamodel.managedType(parentMap.getModel().getBindableJavaType()), metamodel, parentMap.getJavaType(), internalCorrelate((FromImpl) parentMap), parentMap.getModel(), parentMap.getJoinType(), (FromImpl) parentMap);
}
protected org.eclipse.persistence.expressions.Expression internalCorrelate(FromImpl from){
org.eclipse.persistence.expressions.Expression expression = ((InternalSelection)from).getCurrentNode();
ExpressionBuilder builder = new ExpressionBuilder(expression.getBuilder().getQueryClass());
org.eclipse.persistence.expressions.Expression correlated = expression.rebuildOn(builder);
expression = expression.equal(correlated);
this.correlations.add(expression);
org.eclipse.persistence.expressions.Expression selectionCriteria = expression.and(this.subQuery.getSelectionCriteria());
this.subQuery.setSelectionCriteria(selectionCriteria);
return correlated;
}
@Override
public Set> getParameters() {
return ((CommonAbstractCriteriaImpl)this.getContainingQuery()).getParameters();
}
/**
* Return the query of which this is a subquery.
* @return the enclosing query or subquery
*/
@Override
public AbstractQuery> getParent(){
if (parent == null || parent instanceof AbstractQuery) {
return (AbstractQuery) this.parent;
}
throw new IllegalStateException("TODO.. write a better message");
}
/**
* 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 Subquery distinct(boolean distinct){
super.distinct(distinct);
if (!distinct){
this.subQuery.setDistinctState(ObjectLevelReadQuery.DONT_USE_DISTINCT);
}else{
this.subQuery.setDistinctState(ObjectLevelReadQuery.USE_DISTINCT);
}
return this;
}
/**
* Returns the current EclipseLink expression at this node in the criteria expression tree
* @return the currentNode
*/
@Override
public org.eclipse.persistence.expressions.Expression getCurrentNode() {
return currentNode;
}
/**
* Return the selection item of the query. This will correspond to the query type.
* @return the selection item of the query
*/
@Override
public Expression getSelection(){
return (Expression) this.selection;
}
/**
* Return the joins that have been made from the subquery.
*
* @return joins made from this type
*/
@Override
public java.util.Set> getCorrelatedJoins(){
return this.correlatedJoins;
}
@Override
public void addParameter(ParameterExpression> parameter){
((CommonAbstractCriteriaImpl)this.getContainingQuery()).addParameter(parameter);
}
@Override
public void addJoin(FromImpl join){
if (this.processedJoins == null ) {
this.processedJoins = new HashSet<>();
}
if (! this.processedJoins.contains(join)){
this.processedJoins.add(join);
this.subQuery.addNonFetchJoinedAttribute(join.getCurrentNode());
}
}
//Expression
@Override
public Expression as(Class type) {
return (Expression) this;
}
@Override
public Expression cast(Class aClass) {
// JPA spec: New instance with provided Java type
return new SubQueryImpl<>(
metamodel, where, queryBuilder, aClass, parameters,
queryResult, distinct, havingClause, groupBy, roots, baseExpression,
selection, currentNode, alias, subQuery, correlatedJoins, parent, processedJoins, correlations);
}
@Override
public Predicate in(Object... values) {
List> list = new ArrayList<>();
list.add(this);
return new CompoundExpressionImpl(this.metamodel, this.currentNode.in(values), list, "in");
}
/**
* Apply a predicate to test whether the expression is a member
* of the argument list.
* @return predicate testing for membership
*/
@Override
public Predicate in(Expression>... values) {
List> list = new ArrayList<>();
list.add(this);
for (Expression exp: values){
if (!((InternalExpression)exp).isLiteral() && !((InternalExpression) exp).isParameter()){
Object[] params = new Object[]{exp};
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("CRITERIA_NON_LITERAL_PASSED_TO_IN",params));
}
}
return new CompoundExpressionImpl(this.metamodel, this.currentNode.in(values), list, "in");
}
/**
* Apply a predicate to test whether the expression is a member
* of the collection.
* @param values collection
* @return predicate testing for membership
*/
@Override
public Predicate in(Collection> values) {
List> list = new ArrayList<>();
list.add(this);
return new CompoundExpressionImpl(this.metamodel, this.currentNode.in(values), list, "in");
}
/**
* Apply a predicate to test whether the expression is a member
* of the collection.
* @param values expression corresponding to collection
* @return predicate testing for membership
*/
@Override
public Predicate in(Expression> values) {
List> list = new ArrayList<>();
list.add(this);
return new CompoundExpressionImpl(this.metamodel, this.currentNode.in(((InternalSelection)values).getCurrentNode()), list, "in");
}
@Override
public Predicate isNotNull() {
List> list = new ArrayList<>();
list.add(this);
return new CompoundExpressionImpl(this.metamodel, this.currentNode.notNull(), list, "not null");
}
@Override
public Predicate equalTo(Expression> value) {
return new CompoundExpressionImpl(
this.metamodel,
this.currentNode.equal(value),
List.of(this, value),
"equals");
}
@Override
public Predicate equalTo(Object value) {
return new CompoundExpressionImpl(
this.metamodel,
this.currentNode.equal(value),
List.of(this, ExpressionImpl.createLiteral(value, metamodel)),
"equals");
}
@Override
public Predicate notEqualTo(Expression> value) {
return new CompoundExpressionImpl(
this.metamodel,
this.currentNode.equal(value),
List.of(this, value),
"not equal");
}
@Override
public Predicate notEqualTo(Object value) {
return new CompoundExpressionImpl(
this.metamodel,
this.currentNode.equal(value),
List.of(this, ExpressionImpl.createLiteral(value, metamodel)),
"not equal");
}
@Override
public Predicate isNull() {
List> list = new ArrayList<>();
list.add(this);
return new CompoundExpressionImpl(this.metamodel, this.currentNode.isNull(), list, "is null");
}
//SELECTION
/**
* Assign an alias to the selection.
*
* @param name
* alias
*/
@Override
public Selection alias(String name) {
this.alias = name;
return this;
}
@Override
public String getAlias() {
return this.alias;
}
@Override
public Class getJavaType() {
return this.queryType;
}
/**
* Return selection items composing a compound selection
* @return list of selection items
* @throws IllegalStateException if selection is not a compound
* selection
*/
@Override
public List> getCompoundSelectionItems(){
throw new IllegalStateException(ExceptionLocalization.buildMessage("CRITERIA_NOT_A_COMPOUND_SELECTION"));
}
/**
* Whether the selection item is a compound selection
* @return boolean
*/
@Override
public boolean isCompoundSelection(){
return false;
}
@Override
public boolean isConstructor(){
return false;
}
@Override
public boolean isJunction(){
return false;
}
@Override
public boolean isPredicate(){
return false;
}
@Override
public boolean isParameter(){
return false;
}
@Override
public boolean isRoot(){
return false;
}
@Override
public boolean isSubquery(){
return true;
}
@Override
protected void integrateRoot(RootImpl root) {
if (this.roots.isEmpty()) {
TypeImpl type = ((MetamodelImpl)this.metamodel).getType(this.queryType);
if ((type != null && type.getPersistenceType() == PersistenceType.ENTITY) || queryType.equals(ClassConstants.OBJECT)) {
// this is the first root, set return type and selection and query type
if (this.selection == null) {
this.selection = root;
this.subQuery.getItems().clear();
this.subQuery.addAttribute("", new ConstantExpression(1, root.getCurrentNode().getBuilder()));
this.queryResult = ResultType.ENTITY;
}
}
this.subQuery.setReferenceClass(root.getJavaType());
this.subQuery.setExpressionBuilder(root.getCurrentNode().getBuilder());
this.queryType = root.getJavaType();
this.currentNode.setBaseExpression(((CommonAbstractCriteriaImpl)this.parent).getBaseExpression());
}
// If the parent of this SubQuery is a CriteriaQuery and the CriteriaQuery has multiple roots,
// assign the baseExpression based on the SubQuery's roots - Bug 509693
if (!this.roots.contains(root) && this.parent instanceof CriteriaQueryImpl &&
((CriteriaQueryImpl) this.parent).getRoots().size() > 1) {
this.currentNode.setBaseExpression(((CriteriaQueryImpl) this.parent).getBaseExpression(root));
}
super.integrateRoot(root);
}
@Override
public boolean isCompoundExpression(){
return false;
}
@Override
public boolean isExpression(){
return true;
}
@Override
public boolean isFrom(){
return false;
}
@Override
public boolean isLiteral(){
return false;
}
@Override
public void findRootAndParameters(CommonAbstractCriteriaImpl query){
for (Join join: this.correlatedJoins){
((JoinImpl)join).findRootAndParameters(query);
}
}
@Override
protected org.eclipse.persistence.expressions.Expression getBaseExpression() {
return this.currentNode.getSubQuery().getExpressionBuilder();
}
@Override
public CommonAbstractCriteria getContainingQuery() {
return this.parent;
}
@Override
public DatabaseQuery getDatabaseQuery() {
return this.subQuery;
}
}