blob: 3c61281ead41d3914e3307f672ff93114bda7265 [file] [log] [blame]
/*
* 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.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.helper.NonSynchronizedVector;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.querykeys.ForeignReferenceQueryKey;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.ReadQuery;
/**
* Superclass for any object type expressions.
*/
public abstract class ObjectExpression extends DataExpression {
protected transient ClassDescriptor descriptor;
public List<Expression> derivedExpressions;
/** indicates whether subclasses should be joined */
protected boolean shouldUseOuterJoinForMultitableInheritance;
/** Is this query key to be resolved using an outer join or not. Does not apply to attributes. */
protected boolean shouldUseOuterJoin;
/** Allow an expression node to be cast to a subclass or implementation class. */
protected Class castClass;
/** Defines that this expression has been joined to the source expression. */
protected Expression joinSource;
/** Allow for an ON clause to be specified on a join condition. */
protected Expression onClause;
/** Used to track the index of the OuterJoinExpressionHolder that might be associated to this expression */
private Integer outerJoinExpIndex = null;
/** Allow hasBeenAliased to be marked independently from the existence of the tableAliases collection. */
protected boolean hasBeenAliased = false;
protected ObjectExpression() {
this.shouldUseOuterJoin = false;
}
/**
* Return an expression that allows you to treat its base as if it were a subclass of the class returned by the base
* This can only be called on an ExpressionBuilder, the result of expression.get(String), expression.getAllowingNull(String),
* the result of expression.anyOf("String") or the result of expression.anyOfAllowingNull("String")
*
* downcast uses Expression.type() internally to guarantee the results are of the specified class.
* <p>Example:
* <pre>
* Expression: employee.get("project").as(LargeProject.class).get("budget").equal(1000)
* Java: ((LargeProject)employee.getProjects().get(0)).getBudget() == 1000
* SQL: LPROJ.PROJ_ID (+)= PROJ.PROJ_ID AND L_PROJ.BUDGET = 1000 AND PROJ.TYPE = "L"
* </pre>
*/
@Override
public Expression treat(Class castClass){
//to be used on expressionBuilders
QueryKeyExpression clonedExpression = new TreatAsExpression(castClass, this);
clonedExpression.shouldQueryToManyRelationship = false;
clonedExpression.hasQueryKey = false;
clonedExpression.hasMapping = false;
this.addDerivedExpression(clonedExpression);
return clonedExpression;
}
/**
* 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 expression) {
if (this == expression) {
return true;
}
if (!super.equals(expression)) {
return false;
}
if ((this.onClause != null) || (((ObjectExpression)expression).onClause != null)) {
return false;
}
return this.shouldUseOuterJoin == ((ObjectExpression)expression).shouldUseOuterJoin;
}
/**
* INTERNAL:
* Add the expression as a derived child of this expression.
* i.e. e.get("name"), "name" is a derived child of "e".
*/
public synchronized void addDerivedExpression(Expression addThis) {
if (this.derivedExpressions == null) {
this.derivedExpressions = new ArrayList();
}
this.derivedExpressions.add(addThis);
}
/**
* INTERNAL:
* Return the expression to join the main table of this node to any auxiliary tables.
*/
public Expression additionalExpressionCriteria() {
if (getDescriptor() == null) {
return null;
}
Expression criteria = getDescriptor().getQueryManager().getAdditionalJoinExpression();
if(getSession().getPlatform().shouldPrintOuterJoinInWhereClause()) {
if(isUsingOuterJoinForMultitableInheritance()) {
Expression childrenCriteria = getDescriptor().getInheritancePolicy().getChildrenJoinExpression();
childrenCriteria = getBaseExpression().twist(childrenCriteria, this);
childrenCriteria.convertToUseOuterJoin();
if(criteria == null) {
criteria = childrenCriteria;
} else {
criteria = criteria.and(childrenCriteria);
}
}
}
return criteria;
}
/**
* INTERNAL:
* Used in case outer joins should be printed in FROM clause.
* Each of the additional tables mapped to expressions that joins it.
*/
public Map additionalExpressionCriteriaMap() {
if (getDescriptor() == null) {
return null;
}
HashMap tablesJoinExpressions = null;
if(isUsingOuterJoinForMultitableInheritance()) {
tablesJoinExpressions = new HashMap();
List<DatabaseTable> childrenTables = getDescriptor().getInheritancePolicy().getChildrenTables();
for( int i=0; i < childrenTables.size(); i++) {
DatabaseTable table = childrenTables.get(i);
Expression joinExpression = getDescriptor().getInheritancePolicy().getChildrenTablesJoinExpressions().get(table);
if (getBaseExpression() != null){
joinExpression = getBaseExpression().twist(joinExpression, this);
} else {
joinExpression = twist(joinExpression, this);
}
tablesJoinExpressions.put(table, joinExpression);
}
}
return tablesJoinExpressions;
}
/**
* INTERNAL:
* Assign aliases to any tables which I own. Start with t(initialValue),
* and return the new value of the counter , i.e. if initialValue is one
* and I have tables ADDRESS and EMPLOYEE I will assign them t1 and t2 respectively, and return 3.
*/
@Override
public int assignTableAliasesStartingAt(int initialValue) {
//This differs from the Expression implementation only in that it must set the hasBeenAliased flag when done.
int returnVal = super.assignTableAliasesStartingAt(initialValue);
this.hasBeenAliased = true;
return returnVal;
}
/**
* PUBLIC:
* Return an expression representing traversal of a 1:many or many:many relationship.
* This allows you to query whether any of the "many" side of the relationship satisfies the remaining criteria.
* <p>Example:
* <pre>
* Expression: employee.anyOf("managedEmployees").get("firstName").equal("Bob")
* Java: no direct equivalent
* SQL: SELECT DISTINCT ... WHERE (t2.MGR_ID = t1.ID) AND (t2.F_NAME = 'Bob')
* </pre>
* @param shouldJoinBeIndependent indicates whether a new expression should be created.
*/
@Override
public Expression anyOf(String attributeName, boolean shouldJoinBeIndependent) {
QueryKeyExpression queryKey;
if (shouldJoinBeIndependent) {
queryKey = newDerivedExpressionNamed(attributeName);
} else {
queryKey = derivedExpressionNamed(attributeName);
}
queryKey.doQueryToManyRelationship();
return queryKey;
}
/**
* ADVANCED:
* Return an expression representing traversal of a 1:many or many:many relationship.
* This allows you to query whether any of the "many" side of the relationship satisfies the remaining criteria.
* <p>Example:
* <pre>
* Expression: employee.anyOf("managedEmployees").get("firstName").equal("Bob")
* Java: no direct equivalent
* SQL: SELECT DISTINCT ... WHERE (t2.MGR_ID (+) = t1.ID) AND (t2.F_NAME = 'Bob')
* </pre>
* @param shouldJoinBeIndependent indicates whether a new expression should be created.
*/
@Override
public Expression anyOfAllowingNone(String attributeName, boolean shouldJoinBeIndependent) {
QueryKeyExpression queryKey;
if (shouldJoinBeIndependent) {
queryKey = newDerivedExpressionNamed(attributeName);
} else {
queryKey = derivedExpressionNamed(attributeName);
}
queryKey.doUseOuterJoin();
queryKey.doQueryToManyRelationship();
return queryKey;
}
/**
* INTERNAL
* Return true if it uses a cast class and query is downcasting. It will
* look into inheritance hierarchy of the root descriptor.
*/
public boolean isDowncast(ClassDescriptor rootDescriptor, AbstractSession session) {
if (castClass == null){
return false;
}
if (rootDescriptor.getJavaClass() == castClass){
return false;
}
ClassDescriptor castDescriptor = session.getClassDescriptor(castClass);
if (castDescriptor == null){
throw QueryException.couldNotFindCastDescriptor(castClass, getBaseExpression());
}
if (castDescriptor.getInheritancePolicy() == null){
throw QueryException.castMustUseInheritance(getBaseExpression());
}
ClassDescriptor parentDescriptor = castDescriptor.getInheritancePolicy().getParentDescriptor();
while (parentDescriptor != null){
if (parentDescriptor == rootDescriptor){
return true;
}
parentDescriptor = parentDescriptor.getInheritancePolicy().getParentDescriptor();
}
throw QueryException.couldNotFindCastDescriptor(castClass, getBaseExpression());
}
/**
* INTERNAL
* Return true if treat was used on this expression
*/
public boolean isTreatUsed() {
if (this.hasDerivedExpressions() )
for (Expression exp: this.derivedExpressions) {
if (exp.isTreatExpression()) {
return true;
}
}
return false;
}
/**
* INTERNAL
* Return the descriptor which contains this query key, look in the inheritance hierarchy
* of rootDescriptor for the descriptor.
*/
public ClassDescriptor convertToCastDescriptor(ClassDescriptor rootDescriptor, AbstractSession session) {
if (castClass == null || rootDescriptor == null || rootDescriptor.getJavaClass() == castClass) {
return rootDescriptor;
}
ClassDescriptor castDescriptor = session.getClassDescriptor(castClass);
if (castDescriptor == null){
throw QueryException.couldNotFindCastDescriptor(castClass, getBaseExpression());
}
if (!castDescriptor.hasInheritance()){
throw QueryException.castMustUseInheritance(getBaseExpression());
}
ClassDescriptor parentDescriptor = castDescriptor.getInheritancePolicy().getParentDescriptor();
while (parentDescriptor != null){
if (parentDescriptor == rootDescriptor){
return castDescriptor;
}
parentDescriptor = parentDescriptor.getInheritancePolicy().getParentDescriptor();
}
ClassDescriptor childDescriptor = rootDescriptor;
while (childDescriptor != null){
if (childDescriptor == castDescriptor){
return rootDescriptor;
}
childDescriptor = childDescriptor.getInheritancePolicy().getParentDescriptor();
}
throw QueryException.couldNotFindCastDescriptor(castClass, getBaseExpression());
}
public List<Expression> copyDerivedExpressions(Map alreadyDone) {
if (this.derivedExpressions == null) {
return null;
}
List<Expression> derivedExpressionsCopy;
synchronized(this) {
derivedExpressionsCopy = new ArrayList(this.derivedExpressions);
}
List<Expression> result = new ArrayList(derivedExpressionsCopy.size());
for (Expression exp : derivedExpressionsCopy) {
result.add(exp.copiedVersionFrom(alreadyDone));
}
return result;
}
public QueryKeyExpression derivedExpressionNamed(String attributeName) {
QueryKeyExpression existing = existingDerivedExpressionNamed(attributeName);
if (existing != null) {
return existing;
}
return newDerivedExpressionNamed(attributeName);
}
public Expression derivedManualExpressionNamed(String attributeName, ClassDescriptor aDescriptor) {
Expression existing = existingDerivedExpressionNamed(attributeName);
if (existing != null) {
return existing;
}
return newManualDerivedExpressionNamed(attributeName, aDescriptor);
}
public void doNotUseOuterJoin() {
shouldUseOuterJoin = false;
}
public void doUseOuterJoin() {
shouldUseOuterJoin = true;
}
public QueryKeyExpression existingDerivedExpressionNamed(String attributeName) {
if (this.derivedExpressions == null) {
return null;
}
List<Expression> derivedExpressionsCopy;
synchronized(this) {
derivedExpressionsCopy = new ArrayList(this.derivedExpressions);
}
for (Expression derivedExpression : derivedExpressionsCopy) {
QueryKeyExpression exp = (QueryKeyExpression)derivedExpression;
if (exp.getName().equals(attributeName)) {
return exp;
}
}
return null;
}
/**
* Return the expression from the attribute dervied from this expression.
*/
@Override
public Expression get(String attributeName, boolean forceInnerJoin) {
ObjectExpression result = derivedExpressionNamed(attributeName);
if (forceInnerJoin) {
result.doNotUseOuterJoin();
}
return result;
}
/**
* Defines a join between this expression and the target expression based on the ON clause.
*/
@Override
public Expression leftJoin(Expression target, Expression onClause) {
join(target, onClause);
((ObjectExpression)target).doUseOuterJoin();
return this;
}
/**
* Defines a join between this expression and the target expression based on the ON clause.
*/
@Override
public Expression join(Expression target, Expression onClause) {
if (target instanceof ObjectExpression) {
((ObjectExpression)target).setJoinSource(this);
((ObjectExpression)target).setOnClause(onClause);
} else {
throw new IllegalArgumentException();
}
return this;
}
@Override
public Expression getAllowingNull(String attributeName) {
ObjectExpression exp = existingDerivedExpressionNamed(attributeName);
// The same (aliased) table cannot participate in a normal join and an outer join.
// To help enforce this, if the node already exists
if (exp != null) {
return exp;
}
ObjectExpression result = derivedExpressionNamed(attributeName);
result.doUseOuterJoin();
return result;
}
public Class getCastClass() {
return castClass;
}
/**
* PUBLIC:
* Return an expression that wraps the inheritance type field in an expression.
* <p>Example:
* <pre>
* builder.getClassForInheritance().equal(SmallProject.class);
* </pre>
*/
@Override
public Expression type() {
return new ClassTypeExpression(this);
}
@Override
public ClassDescriptor getDescriptor() {
if (isAttribute()) {
return null;
}
if (descriptor == null) {
// Look first for query keys, then mappings. Ultimately we should have query keys
// for everything and can dispense with the mapping part.
ForeignReferenceQueryKey queryKey = (ForeignReferenceQueryKey)getQueryKeyOrNull();
if (queryKey != null) {
descriptor = convertToCastDescriptor(getSession().getDescriptor(queryKey.getReferenceClass()), getSession());
return descriptor;
}
if (getMapping() == null) {
throw QueryException.invalidQueryKeyInExpression(this);
}
// We assume this is either a foreign reference or an aggregate mapping
descriptor = getMapping().getReferenceDescriptor();
if (getMapping().isVariableOneToOneMapping()) {
throw QueryException.cannotQueryAcrossAVariableOneToOneMapping(getMapping(), descriptor);
}
descriptor = convertToCastDescriptor(descriptor, getSession());
}
return descriptor;
}
/**
* INTERNAL: Not to be confused with the public getField(String)
* This returns a collection of all fields associated with this object. Really
* only applies to query keys representing an object or to expression builders.
*/
@Override
public Vector getFields() {
if (getDescriptor() == null) {
DatabaseMapping mapping = getMapping();
if (mapping != null) {
return mapping.getSelectFields();
}
return new NonSynchronizedVector(0);
}
if (descriptor.hasInheritance() && descriptor.getInheritancePolicy().shouldReadSubclasses()
&& (!descriptor.getInheritancePolicy().hasMultipleTableChild()) || shouldUseOuterJoinForMultitableInheritance()) {
// return all fields because we can.
return descriptor.getAllFields();
} else {
return descriptor.getFields();
}
}
/**
* INTERNAL:
*/
@Override
public List<DatabaseField> getSelectionFields(ReadQuery query) {
if (getDescriptor() == null) {
DatabaseMapping mapping = getMapping();
if (mapping != null) {
return mapping.getSelectFields();
}
return new ArrayList<>(0);
}
if (descriptor.hasInheritance() && descriptor.getInheritancePolicy().shouldReadSubclasses()
&& (!descriptor.getInheritancePolicy().hasMultipleTableChild()) || shouldUseOuterJoinForMultitableInheritance()) {
// return all fields because we can.
if (query != null && query.isObjectLevelReadQuery()) {
return descriptor.getAllSelectionFields((ObjectLevelReadQuery)query);
} else {
return descriptor.getAllSelectionFields();
}
} else {
if (query != null && query.isObjectLevelReadQuery()) {
return descriptor.getSelectionFields((ObjectLevelReadQuery)query);
} else {
return descriptor.getSelectionFields();
}
}
}
/**
* INTERNAL:
* Returns the first field from each of the owned tables, used for
* fine-grained pessimistic locking.
*/
protected Vector getForUpdateOfFields() {
Vector allFields = getFields();
int expected = getTableAliases().size();
Vector firstFields = new Vector(expected);
DatabaseTable lastTable = null;
DatabaseField field = null;
int i = 0;
// The following loop takes O(n*m) time. n=# of fields. m=#tables.
// However, in the m=1 case this will take one pass only.
// Also assuming that fields are generally sorted by table, this will
// take O(n) time.
// An even faster way may be to go getDescriptor().getAdditionalPrimaryKeyFields.
while ((i < allFields.size()) && (firstFields.size() < expected)) {
field = (DatabaseField)allFields.elementAt(i++);
if ((lastTable == null) || !field.getTable().equals(lastTable)) {
lastTable = field.getTable();
int j = 0;
while (j < firstFields.size()) {
if (lastTable.equals(((DatabaseField)firstFields.elementAt(j)).getTable())) {
break;
}
j++;
}
if (j == firstFields.size()) {
firstFields.addElement(field);
}
}
}
return firstFields;
}
public Expression getManualQueryKey(String attributeName, ClassDescriptor aDescriptor) {
return derivedManualExpressionNamed(attributeName, aDescriptor);
}
/**
* Return any tables in addition to the descriptor's tables, such as the mappings join table.
*/
public List<DatabaseTable> getAdditionalTables() {
return null;
}
/**
* Return any tables that are defined by this expression (and not its base).
*/
@Override
public List<DatabaseTable> getOwnedTables() {
ClassDescriptor descriptor = getDescriptor();
List<DatabaseTable> tables = null;
if (descriptor == null) {
List<DatabaseTable> additionalTables = getAdditionalTables();
if (additionalTables == null) {
return null;
} else {
return new ArrayList(additionalTables);
}
} else if (descriptor.isAggregateDescriptor()) {
return null;
} else if ((descriptor.getHistoryPolicy() != null) && (getAsOfClause().getValue() != null)) {
tables = descriptor.getHistoryPolicy().getHistoricalTables();
} else if (isUsingOuterJoinForMultitableInheritance()) {
tables = descriptor.getInheritancePolicy().getAllTables();
} else {
tables = descriptor.getTables();
}
List<DatabaseTable> additionalTables = getAdditionalTables();
if (additionalTables != null) {
tables = new Vector(tables);
Helper.addAllUniqueToList(tables, additionalTables);
return tables;
}
return tables;
}
@Override
public boolean hasBeenAliased() {
return hasBeenAliased;
}
/**
* INTERNAL:
*/
@Override
public void clearAliases() {
hasBeenAliased = false;
super.clearAliases();
}
protected boolean hasDerivedExpressions() {
return derivedExpressions != null;
}
@Override
public boolean isObjectExpression() {
return true;
}
/**
* INTERNAL:
* indicates whether additional expressions for multitable inheritance should be used and are available
*/
public boolean isUsingOuterJoinForMultitableInheritance() {
return shouldUseOuterJoinForMultitableInheritance() &&
getDescriptor() != null && getDescriptor().hasInheritance() &&
getDescriptor().getInheritancePolicy().hasMultipleTableChild() &&
getDescriptor().getInheritancePolicy().shouldReadSubclasses();
}
public QueryKeyExpression newDerivedExpressionNamed(String attributeName) {
QueryKeyExpression result = new QueryKeyExpression(attributeName, this);
addDerivedExpression(result);
return result;
}
public Expression newManualDerivedExpressionNamed(String attributeName, ClassDescriptor aDescriptor) {
QueryKeyExpression result = new ManualQueryKeyExpression(attributeName, this, aDescriptor);
addDerivedExpression(result);
return result;
}
/**
* INTERNAL:
* Used for cloning.
*/
@Override
protected void postCopyIn(Map alreadyDone) {
super.postCopyIn(alreadyDone);
this.derivedExpressions = copyDerivedExpressions(alreadyDone);
if (this.onClause != null) {
this.onClause = this.onClause.copiedVersionFrom(alreadyDone);
}
if (this.joinSource != null) {
this.joinSource = this.joinSource.copiedVersionFrom(alreadyDone);
}
}
/**
* Return null by default, only QueryKeyExpression can have a relation table.
*/
public DatabaseTable getRelationTable() {
return null;
}
/**
* Return false by default, only possible for QueryKeyExpression.
*/
public boolean isDirectCollection() {
return false;
}
/**
* INTERNAL:
* The method was added to circumvent derivedFields and derivedTables being
* protected.
* @see org.eclipse.persistence.expressions.ExpressionBuilder#registerIn(Map alreadyDone)
*/
public void postCopyIn(Map alreadyDone, List<Expression> oldDerivedFields, List<Expression> oldDerivedTables) {
// bug 2637484 INVALID QUERY KEY EXCEPTION THROWN USING BATCH READS AND PARALLEL EXPRESSIONS
if (oldDerivedFields != null) {
if (this.derivedFields == null) {
this.derivedFields = copyCollection(oldDerivedFields, alreadyDone);
} else {
this.derivedFields.addAll(copyCollection(oldDerivedFields, alreadyDone));
}
}
if (oldDerivedTables != null) {
if (this.derivedTables == null) {
this.derivedTables = copyCollection(oldDerivedTables, alreadyDone);
} else {
this.derivedTables.addAll(copyCollection(oldDerivedTables, alreadyDone));
}
}
}
public Expression getOnClause() {
return onClause;
}
public void setOnClause(Expression onClause) {
this.onClause = onClause;
}
public void setCastClass(Class castClass) {
this.castClass = castClass;
}
/**
* INTERNAL:
* set the flag indicating whether subclasses should be joined
*/
public void setShouldUseOuterJoinForMultitableInheritance(boolean shouldUseOuterJoinForMultitableInheritance) {
this.shouldUseOuterJoinForMultitableInheritance = shouldUseOuterJoinForMultitableInheritance;
}
public boolean shouldUseOuterJoin() {
return shouldUseOuterJoin;
}
public boolean shouldUseOuterJoinForMultitableInheritance() {
return shouldUseOuterJoinForMultitableInheritance;
}
/**
* INTERNAL:
* writes the first field from each of the owned tables, used for
* fine-grained pessimistic locking.
*/
protected void writeForUpdateOfFields(ExpressionSQLPrinter printer, SQLSelectStatement statement) {
for (Iterator iterator = getForUpdateOfFields().iterator(); iterator.hasNext();) {
DatabaseField field = (DatabaseField)iterator.next();
if (printer.getPlatform().shouldPrintAliasForUpdate()) {
writeAlias(printer, field, statement);
} else {
writeField(printer, field, statement);
}
}
}
public Expression getJoinSource() {
return joinSource;
}
public void setJoinSource(Expression joinSource) {
this.joinSource = joinSource;
}
public Integer getOuterJoinExpIndex() {
return outerJoinExpIndex;
}
public void setOuterJoinExpIndex(Integer outerJoinExpIndex) {
this.outerJoinExpIndex = outerJoinExpIndex;
}
/**
* INTERNAL:
* Parses an expression to return the first non-AggregateObjectMapping expression after the base ExpressionBuilder.
* This is used by joining and batch fetch to get the list of mappings that really need to be processed (non-aggregates).
* @param aggregateMappingsEncountered - collection of aggregateObjectMapping expressions encountered in the returned expression
* between the first expression and the ExpressionBuilder
* @return first non-AggregateObjectMapping expression after the base ExpressionBuilder from the fullExpression
*/
public ObjectExpression getFirstNonAggregateExpressionAfterExpressionBuilder(List aggregateMappingsEncountered) {
boolean done = false;
ObjectExpression baseExpression = this;
ObjectExpression prevExpression = this;
while (!baseExpression.getBaseExpression().isExpressionBuilder() && !done) {
baseExpression = (ObjectExpression)baseExpression.getBaseExpression();
while (!baseExpression.isExpressionBuilder() && baseExpression.getMapping().isAggregateObjectMapping()) {
aggregateMappingsEncountered.add(baseExpression.getMapping());
baseExpression = (ObjectExpression)baseExpression.getBaseExpression();
}
if (baseExpression.isExpressionBuilder()) {
done = true;
//use the one closest to the expression builder that wasn't an aggregate
baseExpression = prevExpression;
} else {
prevExpression = baseExpression;
}
}
return baseExpression;
}
}