blob: 150be198f07e18eacc7c45fec1bed9746acd14e6 [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.queries;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.expressions.ExpressionBuilder;
import org.eclipse.persistence.internal.descriptors.ObjectBuilder;
import org.eclipse.persistence.internal.expressions.BaseExpression;
import org.eclipse.persistence.internal.expressions.ForUpdateOfClause;
import org.eclipse.persistence.internal.expressions.ObjectExpression;
import org.eclipse.persistence.internal.expressions.QueryKeyExpression;
import org.eclipse.persistence.internal.helper.NonSynchronizedSubVector;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
import org.eclipse.persistence.queries.Cursor;
import org.eclipse.persistence.queries.FetchGroup;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;
/**
* <p><b>Purpose</b>:
* A common class to be used by ObjectLevelReadQueries and ReportItems. This
* Class will be used to store Joined Attribute Expressions. It will also
* store the indexes for object construction.
*
* @author Gordon Yorke
* @since EJB3.0 RI
*/
public class JoinedAttributeManager implements Cloneable, Serializable {
/** Stores AggregateObjectMapping expressions used within local join expressions */
protected transient List<DatabaseMapping> joinedAggregateMappings = new ArrayList(0);
/** indexed list of mappings corresponding to */
protected transient List<DatabaseMapping> joinedAttributeMappings = new ArrayList(0);
/** Stores the joined attributes added through the query */
protected List<Expression> joinedAttributeExpressions;
/** Stores the joined attributes as specified in the descriptor */
protected List<Expression> joinedMappingExpressions;
/** PERF: Cache the local joined attribute expressions. */
protected List<Expression> joinedAttributes;
/** Used to determine if -m joining has been used. */
protected boolean isToManyJoin = false;
/** PERF: Used to avoid null checks for inner attribute joining. */
protected boolean hasOuterJoinedAttribute = true;
/** Used internally for joining. */
protected transient Map<DatabaseMapping, Object> joinedMappingIndexes;
/** Used internally for joining. */
protected transient Map<DatabaseMapping, ObjectLevelReadQuery> joinedMappingQueries;
/** PERF: Stores the cloned joinedMappingQueries. */
protected transient Map<DatabaseMapping, ObjectLevelReadQuery> joinedMappingQueryClones;
/** Stored all row results to -m joining. */
protected transient List<AbstractRecord> dataResults;
/** Stored all row results to -m joining by cache key. */
protected transient Map<Object, List<AbstractRecord>> dataResultsByPrimaryKey;
/** Stores the descriptor that these joins apply on */
protected transient ClassDescriptor descriptor;
/** Stores the base builder for resolving joined attributes by name. */
protected ExpressionBuilder baseExpressionBuilder;
/** Stores the last used base expression while adding joined attribute expression. */
protected Expression lastJoinedAttributeBaseExpression;
/** Stores the base query. */
protected ObjectBuildingQuery baseQuery;
/** Stores the result index of the parent, used for oneToMany joins. */
protected int parentResultIndex;
/** Determine if duplicate rows should be filter when using 1-m joining. */
protected boolean shouldFilterDuplicates = true;
//** Stores orderBy expressions of the joined CollectionMappings - in case the mapping has listFieldOrder */
protected transient List<Expression> orderByExpressions;
//** Stores additional field expressions of the joined CollectionMappings - in case the mapping has listFieldOrder */
protected transient List<Expression> additionalFieldExpressions;
public JoinedAttributeManager(){
}
public JoinedAttributeManager(ClassDescriptor descriptor, ExpressionBuilder baseBuilder, ObjectBuildingQuery baseQuery){
this.descriptor = descriptor;
this.baseQuery = baseQuery;
this.baseExpressionBuilder = baseBuilder;
this.parentResultIndex = 0;
}
/**
* Return if duplicate rows should be filter when using 1-m joining.
*/
public boolean shouldFilterDuplicates() {
return shouldFilterDuplicates;
}
/**
* Set if duplicate rows should be filter when using 1-m joining.
*/
public void setShouldFilterDuplicates(boolean shouldFilterDuplicates) {
this.shouldFilterDuplicates = shouldFilterDuplicates;
}
public void addJoinedAttribute(Expression attributeExpression) {
this.getJoinedAttributes().add(attributeExpression);
}
public void addJoinedAttributeExpression(Expression attributeExpression) {
if(!getJoinedAttributeExpressions().contains(attributeExpression)) {
getJoinedAttributeExpressions().add(attributeExpression);
}
}
/**
* Add an attribute represented by the given attribute name to the list of joins for this query.
* Note: Mapping level joins are represented separately from query level joins.
*/
public void addJoinedMappingExpression(Expression mappingExpression) {
getJoinedMappingExpressions().add(mappingExpression);
}
/**
* Add an attribute represented by the given attribute name to the list of joins for this query.
* Note: Mapping level joins are represented separately from query level joins.
*/
public void addJoinedMapping(String attributeName) {
addJoinedMappingExpression(this.baseExpressionBuilder.get(attributeName));
}
/**
* Clones the Joined Attribute Manager. Generally called from Query.clone().
*/
@Override
public JoinedAttributeManager clone(){
JoinedAttributeManager joinManager = null;
try {
joinManager = (JoinedAttributeManager)super.clone();
} catch (CloneNotSupportedException exception) {
throw new InternalError(exception.toString());
}
if (this.joinedAttributeExpressions != null) {
joinManager.joinedAttributeExpressions = new ArrayList<>(this.joinedAttributeExpressions);
}
if (this.joinedMappingExpressions != null) {
joinManager.joinedMappingExpressions = new ArrayList<>(this.joinedMappingExpressions);
}
if (this.joinedAttributes != null) {
joinManager.joinedAttributes = new ArrayList<>(this.joinedAttributes);
}
if (this.joinedMappingIndexes != null) {
joinManager.joinedMappingIndexes = new HashMap<>(this.joinedMappingIndexes);
}
if (this.joinedMappingQueries != null) {
joinManager.joinedMappingQueries = new HashMap<>(this.joinedMappingQueries);
}
if (this.orderByExpressions != null) {
joinManager.orderByExpressions = new ArrayList<>(this.orderByExpressions);
}
if (this.additionalFieldExpressions != null) {
joinManager.additionalFieldExpressions = new ArrayList<>(this.additionalFieldExpressions);
}
if (this.joinedAttributeMappings != null) {
joinManager.joinedAttributeMappings = new ArrayList<>(this.joinedAttributeMappings);
}
if (this.joinedAggregateMappings !=null) {
joinManager.joinedAggregateMappings = new ArrayList<>(this.joinedAggregateMappings);
}
return joinManager;
}
/**
* Copies settings from another manager. Should copy all the attributes that clone method clones.
*/
public void copyFrom(JoinedAttributeManager otherJoinManager){
this.joinedAttributeExpressions = otherJoinManager.joinedAttributeExpressions;
this.joinedMappingExpressions = otherJoinManager.joinedMappingExpressions;
this.joinedAttributes = otherJoinManager.joinedAttributes;
this.joinedMappingIndexes = otherJoinManager.joinedMappingIndexes;
this.joinedMappingQueries = otherJoinManager.joinedMappingQueries;
this.orderByExpressions = otherJoinManager.orderByExpressions;
this.additionalFieldExpressions = otherJoinManager.additionalFieldExpressions;
this.joinedAttributeMappings = otherJoinManager.joinedAttributeMappings;
this.joinedAggregateMappings = otherJoinManager.joinedAggregateMappings;
}
/**
* Clear the joining state. This is used to redefine a queries joins for nested joins.
*/
public void clear(){
this.joinedAttributeExpressions = null;
this.joinedMappingExpressions = null;
this.joinedAttributes = null;
this.joinedMappingIndexes = null;
this.isToManyJoin = false;
this.hasOuterJoinedAttribute = false;
this.joinedMappingQueries = null;
this.joinedMappingQueryClones = null;
this.orderByExpressions = null;
this.additionalFieldExpressions = null;
this.joinedAttributeMappings = null;
this.joinedAggregateMappings = null;
}
/**
* For joining the resulting rows include the field/values for many objects.
* As some of the objects may have the same field names, these row partitions need to be calculated.
* The indexes are stored in the query and used later when building the objects.
*/
public int computeJoiningMappingIndexes(boolean includeAllSubclassFields, AbstractSession session, int offset) {
if (!hasJoinedExpressions()) {
return offset;
}
setJoinedMappingIndexes_(new HashMap(getJoinedAttributeExpressions().size() + getJoinedMappingExpressions().size()));
int fieldIndex = 0;
if (getBaseQuery().hasPartialAttributeExpressions()) {
fieldIndex = getDescriptor().getPrimaryKeyFields().size(); // Query will select pks
//next check for any partial attributes that are not joined attributes
Iterator partialAttributes = ((ObjectLevelReadQuery)getBaseQuery()).getPartialAttributeExpressions().iterator();
while(partialAttributes.hasNext()){
Expression expression = (Expression)partialAttributes.next();
if (expression.isQueryKeyExpression()){
if (!getJoinedMappingExpressions().contains(expression) && ! getJoinedAttributeExpressions().contains(expression)){
fieldIndex += ((QueryKeyExpression)expression).getFields().size();
}
}
}
} else if (getBaseQuery().hasExecutionFetchGroup()) {
fieldIndex = ((ObjectLevelReadQuery)getBaseQuery()).getFetchGroupNonNestedFieldsSet().size();
} else {
if (includeAllSubclassFields) {
fieldIndex = getDescriptor().getAllSelectionFields((ObjectLevelReadQuery)getBaseQuery()).size();
} else {
fieldIndex = getDescriptor().getSelectionFields((ObjectLevelReadQuery)getBaseQuery()).size();
}
}
fieldIndex += offset;
fieldIndex = computeIndexesForJoinedExpressions(getJoinedAttributeExpressions(), fieldIndex, session);
fieldIndex = computeIndexesForJoinedExpressions(getJoinedMappingExpressions(), fieldIndex, session);
return fieldIndex;
}
/**
* This method is used when computing the nested queries for joined mappings.
* It recurses computing the nested mapping queries and their join indexes.
*/
protected void computeNestedQueriesForJoinedExpressions(List joinedExpressions, AbstractSession session, ObjectLevelReadQuery readQuery) {
for (int index = 0; index < joinedExpressions.size(); index++) {
ObjectExpression objectExpression = (ObjectExpression)joinedExpressions.get(index);
// Expression may not have been initialized.
objectExpression.getBuilder().setSession(session.getRootSession(null));
if (objectExpression.getBuilder().getQueryClass() == null){
objectExpression.getBuilder().setQueryClass(descriptor.getJavaClass());
}
//get the first expression after the builder that is not an aggregate, and populate the aggregateMapping list if there are aggregates
ObjectExpression baseExpression = objectExpression.getFirstNonAggregateExpressionAfterExpressionBuilder(getJoinedAggregateMappings());
// PERF: Cache local join attribute Expression.
this.addJoinedAttribute(baseExpression);
DatabaseMapping mapping = baseExpression.getMapping();
this.getJoinedAttributeMappings().add(mapping);
// focus on the base expression. Nested queries will handle nested expressions, and only need to be processed once
if (mapping.isForeignReferenceMapping() && !getJoinedMappingQueries_().containsKey(mapping)) {
// A nested query must be built to pass to the descriptor that looks like the real query execution would.
ObjectLevelReadQuery nestedQuery = ((ForeignReferenceMapping)mapping).prepareNestedJoins(this, readQuery, session);
if (nestedQuery != null) {
// Register the nested query to be used by the mapping for all the objects.
getJoinedMappingQueries_().put(mapping, nestedQuery);
}
if (mapping.isCollectionMapping()){
mapping.getContainerPolicy().addNestedJoinsQueriesForMapKey(this, readQuery, session);
}
}
}
}
/**
* Used to optimize joining by pre-computing the nested join queries for the mappings.
*/
public void computeJoiningMappingQueries(AbstractSession session) {
if (hasJoinedExpressions()) {
this.joinedAttributeMappings = new ArrayList<>(getJoinedAttributeExpressions().size() + getJoinedMappingExpressions().size());
this.joinedAttributes = new ArrayList<>(getJoinedAttributeExpressions().size() + getJoinedMappingExpressions().size());
setJoinedMappingQueries_(new HashMap(getJoinedAttributeExpressions().size() + getJoinedMappingExpressions().size()));
computeNestedQueriesForJoinedExpressions(getJoinedAttributeExpressions(), session, (ObjectLevelReadQuery)this.baseQuery);
computeNestedQueriesForJoinedExpressions(getJoinedMappingExpressions(), session, (ObjectLevelReadQuery)this.baseQuery);
}
}
/**
* This method is used when computing the indexes for joined mappings. It iterates through a list of join
* expressions and adds an index that represents where the fields represented by that expression will appear
* in the row returned by a read query.
* Method {@link #computeNestedQueriesForJoinedExpressions(List, AbstractSession, ObjectLevelReadQuery)}
* must be already called.
* @param joinedExpressions Join expressions {@link List}.
* @param currentIndex Current joined mapping index.
* @param session Current session.
* @return Current joined mapping index updated.
*/
protected int computeIndexesForJoinedExpressions(final List joinedExpressions, int currentIndex,
final AbstractSession session) {
for (int index = 0; index < joinedExpressions.size(); index++) {
final ObjectExpression objectExpression = (ObjectExpression)joinedExpressions.get(index);
final DatabaseMapping mapping = objectExpression.getMapping();
// Only store the index if this is the local expression to avoid it being added multiple times.
// This means the base local expression must be first on the list, followed by nested expressions.
final ObjectExpression localExpression = objectExpression
.getFirstNonAggregateExpressionAfterExpressionBuilder(new ArrayList(1));
if ((localExpression == objectExpression) && (mapping != null) && mapping.isForeignReferenceMapping()) {
getJoinedMappingIndexes_().put(mapping, currentIndex);
}
final ClassDescriptor descriptor = mapping.getReferenceDescriptor();
int numberOfFields = 0;
if (descriptor == null) {
// Direct-collection mappings do not have descriptor.
if (mapping.isDirectCollectionMapping()) {
numberOfFields = 1;
}
} else {
final ObjectLevelReadQuery nestedQuery = getNestedJoinedMappingQuery(objectExpression);
FetchGroup fetchGroup = null;
if(descriptor.hasFetchGroupManager()) {
fetchGroup = nestedQuery.getExecutionFetchGroup();
}
if(fetchGroup != null) {
numberOfFields = nestedQuery.getFetchGroupNonNestedFieldsSet(mapping).size();
} else {
if (objectExpression.isQueryKeyExpression()
&& objectExpression.isUsingOuterJoinForMultitableInheritance()) {
numberOfFields = descriptor.getAllSelectionFields(nestedQuery).size();
} else if(objectExpression.isQueryKeyExpression() && objectExpression.getDescriptor() != null
&& objectExpression.getDescriptor().hasInheritance()
&& objectExpression.getDescriptor().getInheritancePolicy().shouldReadSubclasses()) {
numberOfFields = descriptor.getAllFields().size();
} else {
numberOfFields = descriptor.getSelectionFields(nestedQuery).size();
}
}
}
if (mapping.isCollectionMapping()){
// map keys are indexed within the collection's row.
// Therefore we use an offset from within the collections row
numberOfFields += mapping.getContainerPolicy()
.updateJoinedMappingIndexesForMapKey(getJoinedMappingIndexes_(), numberOfFields);
}
currentIndex = currentIndex + numberOfFields;
}
return currentIndex;
}
/**
* Get the list of additional field expressions.
*/
public List<Expression> getAdditionalFieldExpressions() {
if (this.additionalFieldExpressions == null){
this.additionalFieldExpressions = new ArrayList<>();
}
return additionalFieldExpressions;
}
/**
* Get the list of additional field expressions.
*/
public List<Expression> getAdditionalFieldExpressions_() {
return additionalFieldExpressions;
}
/**
* Returns the base expression builder for this query.
*/
public ExpressionBuilder getBaseExpressionBuilder(){
return this.baseExpressionBuilder;
}
/**
* Returns the base query.
*/
public ObjectBuildingQuery getBaseQuery(){
return this.baseQuery;
}
/**
* Return all of the rows fetched by the query, used for 1-m joining.
*/
public List<AbstractRecord> getDataResults_() {
return dataResults;
}
public ClassDescriptor getDescriptor(){
if (this.descriptor == null){
this.descriptor = this.baseQuery.getDescriptor();
}
return this.descriptor;
}
/**
* Return if there are additional field expressions.
*/
public boolean hasAdditionalFieldExpressions() {
return (this.additionalFieldExpressions != null) && (!this.additionalFieldExpressions.isEmpty());
}
/**
* Set the list of additional field expressions.
*/
public void setAdditionalFieldExpressions_(List<Expression> expressions) {
this.additionalFieldExpressions = expressions;
}
/**
* Return the attributes that must be joined.
*/
public List<DatabaseMapping> getJoinedAggregateMappings() {
if (this.joinedAggregateMappings == null){
this.joinedAggregateMappings = new ArrayList<>();
}
return joinedAggregateMappings;
}
/**
* Return the attributes that must be joined.
*/
public List<Expression> getJoinedAttributeExpressions() {
if (this.joinedAttributeExpressions == null){
this.joinedAttributeExpressions = new ArrayList<>();
}
return joinedAttributeExpressions;
}
/**
* Return the attributes that must be joined.
*/
public List<DatabaseMapping> getJoinedAttributeMappings() {
if (this.joinedAttributeMappings == null){
this.joinedAttributeMappings = new ArrayList<>();
}
return this.joinedAttributeMappings;
}
/**
* Return the attributes that must be joined.
*/
public List<Expression> getJoinedAttributes() {
if (this.joinedAttributes == null){
this.joinedAttributes = new ArrayList<>();
}
return this.joinedAttributes;
}
/**
* Get the list of expressions that represent elements that are joined because of their
* mapping for this query.
*/
public List<Expression> getJoinedMappingExpressions() {
if (this.joinedMappingExpressions == null){
this.joinedMappingExpressions = new ArrayList<>();
}
return joinedMappingExpressions;
}
/**
* Return the attributes that must be joined.
*/
public boolean hasJoinedAttributeExpressions() {
return (this.joinedAttributeExpressions != null) && (!this.joinedAttributeExpressions.isEmpty());
}
/**
* This method checks both attribute expressions and mapping expressions and
* determines if there are any joins to be made.
*/
public boolean hasJoinedExpressions() {
return hasJoinedAttributeExpressions() || hasJoinedMappingExpressions();
}
/**
* Return the attributes that must be joined.
*/
public boolean hasJoinedMappingExpressions() {
return (this.joinedMappingExpressions != null) && (!this.joinedMappingExpressions.isEmpty());
}
/**
* Return if any attributes are joined. This is a convience method that
* is only valid after prepare.
*/
public boolean hasJoinedAttributes() {
return (this.joinedAttributes != null) && (!this.joinedAttributes.isEmpty());
}
/**
* PERF: Return if the query uses any outer attribute joins, used to avoid null checks in building objects.
*/
public boolean hasOuterJoinedAttributeQuery() {
return this.hasOuterJoinedAttribute;
}
/**
* Get the list of orderBy expressions.
*/
public List<Expression> getOrderByExpressions() {
if (this.orderByExpressions == null){
this.orderByExpressions = new ArrayList<>();
}
return orderByExpressions;
}
/**
* Get the list of orderBy expressions.
*/
public List<Expression> getOrderByExpressions_() {
return orderByExpressions;
}
/**
* INTERNAL:
* Helper method to get the value from the clone for the expression passed in, triggering joins on
* all intermediate steps.
* Example expression "emp.project.pk" with a clone Employee will trigger indirection and return
* the project pk value.
*/
public Object getValueFromObjectForExpression(AbstractSession session, Object clone, ObjectExpression expression){
if (!expression.isExpressionBuilder()){
//can only operate over querykeys representing aggregate Objects. Indirection should not be needed
Object baseValue = this.getValueFromObjectForExpression(session, clone, (ObjectExpression)expression.getBaseExpression());
if ( baseValue == null ) {
return null;
}
DatabaseMapping mapping = expression.getMapping();
Object attributeValue = mapping.getRealAttributeValueFromObject(baseValue, session);
if (attributeValue != null) {
if (mapping.isForeignReferenceMapping() && (((ForeignReferenceMapping)mapping).getIndirectionPolicy().usesTransparentIndirection())) {
//getRealAttributeValueFromObject does not trigger transparent indirection, but instantiateObject will (it calls size on it)
((ForeignReferenceMapping)mapping).getIndirectionPolicy().instantiateObject(baseValue, attributeValue);
}
}
return attributeValue;
}
return clone;
}
/**
* Return if there are orderBy expressions.
*/
public boolean hasOrderByExpressions() {
return (this.orderByExpressions != null) && (!this.orderByExpressions.isEmpty());
}
/**
* Set the list of orderBy expressions.
*/
public void setOrderByExpressions_(List<Expression> expressions) {
this.orderByExpressions = expressions;
}
/**
* Return if the query uses any -m joins, and thus return duplicate/multiple rows.
*/
public boolean isToManyJoin() {
return this.isToManyJoin;
}
/**
* Return if the attribute is specified for joining.
*/
public boolean isAttributeJoined(ClassDescriptor mappingDescriptor, DatabaseMapping attributeMapping) {
// Since aggregates share the same query as their parent, must avoid the aggregate thinking
// the parents mappings is for it, (queries only share if the aggregate was not joined).
//This isn't taking into account inheritance - a query on a child may use/join parent level mappings
if (this.hasJoinedAttributes()) {
//if it has joined attributes, the other collections must also be set and so don't need to be checked
if (attributeMapping.isAggregateMapping()){
return this.getJoinedAggregateMappings().contains(attributeMapping);
} else {
return this.getJoinedAttributeMappings().contains(attributeMapping);
}}
return isAttributeExpressionJoined(attributeMapping) || isAttributeMappingJoined(attributeMapping);
}
/**
* Iterate through a list of expressions searching for the given attribute name.
* Return true if it is found, false otherwise. Only use if the query was preprepared so that join expressions
* were processed.
*/
protected boolean isMappingInJoinedExpressionList(DatabaseMapping attributeMapping, List joinedExpressionList) {
for (Iterator joinEnum = joinedExpressionList.iterator(); joinEnum.hasNext();) {
List aggregateMappings = new ArrayList();
ObjectExpression expression = ((ObjectExpression)joinEnum.next()).getFirstNonAggregateExpressionAfterExpressionBuilder(aggregateMappings);
if (attributeMapping.isAggregateObjectMapping() && aggregateMappings.contains(attributeMapping)) {
return true;
} else if (attributeMapping.equals(expression.getMapping())) {//expression may not have been processed yet
return true;
}
}
return false;
}
/**
* Iterate through a list of expressions searching for the given attribute name.
* Return true if it is found, false otherwise.
*/
protected boolean isAttributeNameInJoinedExpressionList(String attributeName, List joinedExpressionList) {
for (Iterator joinEnum = joinedExpressionList.iterator(); joinEnum.hasNext();) {
QueryKeyExpression expression = (QueryKeyExpression)joinEnum.next();
while (!expression.getBaseExpression().isExpressionBuilder()) {
expression = (QueryKeyExpression)expression.getBaseExpression();
}
if (expression.getName().equals(attributeName)) {
return true;
}
}
return false;
}
/**
* Return if the attribute is specified for joining.
*/
protected boolean isAttributeExpressionJoined(DatabaseMapping attributeMapping) {
return isMappingInJoinedExpressionList(attributeMapping, getJoinedAttributeExpressions());
}
/**
* Return whether the given attribute is joined as a result of a join on a mapping
*/
protected boolean isAttributeMappingJoined(DatabaseMapping attributeMapping) {
return isAttributeNameInJoinedExpressionList(attributeMapping.getAttributeName(), getJoinedMappingExpressions());
}
/**
* Set the list of expressions that represent elements that are joined because of their
* mapping for this query.
*/
public void setJoinedAttributeExpressions_(List joinedExpressions) {
this.joinedAttributeExpressions = joinedExpressions;
}
/**
* Set the list of expressions that represent elements that are joined because of their
* mapping for this query.
*/
public void setJoinedMappingExpressions_(List joinedMappingExpressions) {
this.joinedMappingExpressions = joinedMappingExpressions;
}
/**
* Return the joined mapping indexes, used to compute mapping row partitions.
*/
public Map<DatabaseMapping, Object> getJoinedMappingIndexes_() {
return joinedMappingIndexes;
}
/**
* Return the joined mapping queries, used optimize joining, only compute the nested queries once.
*/
public Map<DatabaseMapping, ObjectLevelReadQuery> getJoinedMappingQueries_() {
return joinedMappingQueries;
}
/**
* INTERNAL:
* Returns the nested query corresponding to the expression.
* The passed expression should be either join mapping or joined attribute expression.
*/
public ObjectLevelReadQuery getNestedJoinedMappingQuery(Expression expression) {
// the first element of the list is the passed expression,
// next one is its base, ...
// the last one's base is ExpressionBuilder.
ObjectExpression currentExpression = (ObjectExpression)expression;
ArrayList<Expression> expressionBaseList = new ArrayList();
do {
//skip aggregates since they do not have nested query objects added to JoinedMappingQueries, instead
//reference mappings on aggregates are added to the parent's joinAttributeManager
if (!currentExpression.getMapping().isAggregateObjectMapping()){
expressionBaseList.add(currentExpression);
}
currentExpression = (ObjectExpression)currentExpression.getBaseExpression();
} while(!currentExpression.isExpressionBuilder());
// the last expression in the list is not nested - its mapping should have corresponding nestedQuery.
DatabaseMapping currentMapping = ((QueryKeyExpression)expressionBaseList.get(expressionBaseList.size() - 1)).getMapping();
ObjectLevelReadQuery nestedQuery = getJoinedMappingQueries_().get(currentMapping);
// unless the passed expression was not nested, repeat moving up the list.
// the last step is the passed expression (first on the list) getting nested query corresponding to its mapping.
for(int i = expressionBaseList.size() - 2; i >= 0; i--) {
currentMapping = ((QueryKeyExpression)expressionBaseList.get(i)).getMapping();
nestedQuery = nestedQuery.getJoinedAttributeManager().getJoinedMappingQueries_().get(currentMapping);
}
return nestedQuery;
}
/**
* Set the joined mapping queries, used optimize joining, only compute the nested queries once.
*/
public void setJoinedMappingQueries_(Map joinedMappingQueries) {
this.joinedMappingQueries = joinedMappingQueries;
}
/**
* Set the joined mapping indexes, used to compute mapping row partitions.
*/
public void setJoinedMappingIndexes_(Map joinedMappingIndexes) {
this.joinedMappingIndexes = joinedMappingIndexes;
}
/**
* PERF: Set if the query uses any outer attribute joins, used to avoid null checks in building objects.
*/
protected void setIsOuterJoinedAttributeQuery(boolean isOuterJoinedAttribute) {
this.hasOuterJoinedAttribute = isOuterJoinedAttribute;
}
/**
* Set if the query uses any -m joins, and thus return duplicate/multiple rows.
*/
public void setIsToManyJoinQuery(boolean isToManyJoin) {
this.isToManyJoin = isToManyJoin;
}
/**
* Validate and prepare join expressions.
*/
public void prepareJoinExpressions(AbstractSession session) {
// The prepareJoinExpression check for outer-joins to set this to true.
setIsOuterJoinedAttributeQuery(false);
Expression lastJoinedAttributeBaseExpression = null;
List groupedExpressionList = new ArrayList(getJoinedAttributeExpressions().size());
for (int index = 0; index < getJoinedAttributeExpressions().size(); index++) {
Expression expression = getJoinedAttributeExpressions().get(index);
expression = prepareJoinExpression(expression, session);
//EL bug 307497: break base expressions out onto the list and sort/group expressions by base expression
lastJoinedAttributeBaseExpression = addExpressionAndBaseToGroupedList(expression, groupedExpressionList, lastJoinedAttributeBaseExpression);
}
//use the grouped list instead of the original
this.setJoinedAttributeExpressions_(groupedExpressionList);
for (int index = 0; index < getJoinedMappingExpressions().size(); index++) {
Expression expression = getJoinedMappingExpressions().get(index);
expression = prepareJoinExpression(expression, session);
getJoinedMappingExpressions().set(index, expression);
}
}
/**
* adds expression and its base expressions recursively to the expressionList in groups, so that an expression is never listed before
* its base expression
*/
protected Expression addExpressionAndBaseToGroupedList(Expression expression, List expressionlist, Expression lastJoinedAttributeBaseExpression){
if(!expressionlist.contains(expression)) {
int baseExpressionIndex = -1;
boolean sameBase = false;//better than using instanceof BaseExpression. If its not an objectExpression, it will get an exception in prepare anyway
if((expression.isObjectExpression())) {
Expression baseExpression = ((BaseExpression)expression).getBaseExpression();
//filter out aggregate expressions between this and the next node.
while (!baseExpression.isExpressionBuilder() && ((QueryKeyExpression)baseExpression).getMapping().isAggregateMapping()){
baseExpression = ((BaseExpression)baseExpression).getBaseExpression();
}
if(baseExpression != null && !baseExpression.isExpressionBuilder()) {
addExpressionAndBaseToGroupedList(baseExpression, expressionlist, lastJoinedAttributeBaseExpression);
// EL bug 307497
if (baseExpression != lastJoinedAttributeBaseExpression) {
baseExpressionIndex = getJoinedAttributeExpressions().indexOf(baseExpression);
} else {
sameBase = true;
}
}
}
// EL bug 307497
if (baseExpressionIndex == -1) {
expressionlist.add(expression);
if (!sameBase) {
lastJoinedAttributeBaseExpression = expression;
}
} else {
//Add attributeExpression at baseExpressionIndex + 1.
expressionlist.add(baseExpressionIndex+1, expression);
}
}
return lastJoinedAttributeBaseExpression;
}
/**
* Validate and prepare the join expression.
*/
protected Expression prepareJoinExpression(Expression expression, AbstractSession session) {
// Must be query key expression.
if (!expression.isQueryKeyExpression()) {
throw QueryException.mappingForExpressionDoesNotSupportJoining(expression);
}
QueryKeyExpression objectExpression = (QueryKeyExpression)expression;
// Expression may not have been initialized.
if (objectExpression.getBuilder().getQueryClass() == null) {
objectExpression = (QueryKeyExpression)objectExpression.rebuildOn(this.baseExpressionBuilder);
if (objectExpression.getBuilder().getQueryClass() == null) {
objectExpression.getBuilder().setQueryClass(this.descriptor.getJavaClass());
}
}
objectExpression.getBuilder().setSession(session.getRootSession(null));
// Can only join relationships.
if ((objectExpression.getMapping() == null) || (!objectExpression.getMapping().isJoiningSupported())) {
throw QueryException.mappingForExpressionDoesNotSupportJoining(objectExpression);
}
// Search if any of the expression traverse a 1-m.
ObjectExpression baseExpression = objectExpression;
while (!baseExpression.isExpressionBuilder()) {
//pulled from prepareJoinExpressions
baseExpression.setShouldUseOuterJoinForMultitableInheritance(true);
if (((QueryKeyExpression)baseExpression).shouldQueryToManyRelationship()) {
setIsToManyJoinQuery(true);
}
if (baseExpression.shouldUseOuterJoin()) {
setIsOuterJoinedAttributeQuery(true);
}
baseExpression = (ObjectExpression)baseExpression.getBaseExpression();
}
return objectExpression;
}
/**
* This method collects the Joined Mappings from the descriptor and initializes them.
* Excludes the mapping that are not in the passed mappingsAllowedToJoin set (if it's not null).
*/
public void processJoinedMappings(AbstractSession session) {
Set<String> fetchGroupAttributes = null;
FetchGroup fetchGroup = getBaseQuery().getExecutionFetchGroup();
if(fetchGroup != null) {
fetchGroupAttributes = fetchGroup.getAttributeNames();
}
ObjectBuilder objectBuilder = getDescriptor().getObjectBuilder();
if (objectBuilder.hasJoinedAttributes()) {
List mappingJoinedAttributes = objectBuilder.getJoinedAttributes();
if (!hasJoinedAttributeExpressions()) {
for (int i = 0; i < mappingJoinedAttributes.size(); i++) {
ForeignReferenceMapping mapping = (ForeignReferenceMapping) mappingJoinedAttributes.get(i);
if(fetchGroupAttributes == null || fetchGroupAttributes.contains(mapping.getAttributeName())) {
addAndPrepareJoinedMapping(mapping, session);
}
}
} else {
for (int i = 0; i < mappingJoinedAttributes.size(); i++) {
ForeignReferenceMapping mapping = (ForeignReferenceMapping) mappingJoinedAttributes.get(i);
if (!isAttributeExpressionJoined(mapping)) {
if(fetchGroupAttributes == null || fetchGroupAttributes.contains(mapping.getAttributeName())) {
addAndPrepareJoinedMapping(mapping, session);
}
}
}
}
}
}
/**
* Add the mapping for join fetch, prepare and return the join expression being used.
*/
public Expression addAndPrepareJoinedMapping(ForeignReferenceMapping mapping, AbstractSession session) {
Expression joinMappingExpression = null;
if (mapping.isCollectionMapping()) {
if (mapping.isInnerJoinFetched()) {
joinMappingExpression = getBaseExpressionBuilder().anyOf(mapping.getAttributeName(), false);
} else if (mapping.isOuterJoinFetched()) {
joinMappingExpression = getBaseExpressionBuilder().anyOfAllowingNone(mapping.getAttributeName(), false);
}
} else {
if (mapping.isInnerJoinFetched()) {
joinMappingExpression = getBaseExpressionBuilder().get(mapping.getAttributeName());
} else if (mapping.isOuterJoinFetched()) {
joinMappingExpression = getBaseExpressionBuilder().getAllowingNull(mapping.getAttributeName());
}
}
if (joinMappingExpression != null) {
joinMappingExpression = prepareJoinExpression(joinMappingExpression, session);
addJoinedMappingExpression(joinMappingExpression);
}
return joinMappingExpression;
}
/**
* Reset the JoinedAttributeManager. This will be called when the Query is re-prepared
*/
public void reset(){
this.joinedMappingExpressions = null;
this.joinedAttributes = null;
this.isToManyJoin = false;
this.hasOuterJoinedAttribute = true;
this.joinedMappingIndexes = null;
this.joinedMappingQueries = null;
this.dataResults = null;
this.joinedAttributeMappings = null;
this.joinedAggregateMappings = null;
}
/**
* This method is called from within this package it is used when
* initializing a report Item
*/
public void setBaseQuery(ObjectLevelReadQuery query){
this.baseQuery = query;
}
/**
* This method is called from within this package, it is used when
* initializing a ReportItem
*/
protected void setBaseExpressionBuilder(ExpressionBuilder builder){
this.baseExpressionBuilder = builder;
}
/**
* Return all of the rows fetched by the query by cache-key, used for 1-m joining.
*/
public Map<Object, List<AbstractRecord>> getDataResultsByPrimaryKey() {
return dataResultsByPrimaryKey;
}
/**
* Set all of the rows fetched by the query by cache-key, used for 1-m joining.
*/
protected void setDataResultsByPrimaryKey(Map<Object, List<AbstractRecord>> dataResultsByPrimaryKey) {
this.dataResultsByPrimaryKey = dataResultsByPrimaryKey;
}
/**
* Set all of the rows fetched by the query, used for 1-m joining.
*/
public void setDataResults(List dataResults, AbstractSession session) {
this.dataResults = dataResults;
processDataResults(session);
}
/**
* Process the data-results for joined data for a 1-m join.
* This allows all the data to be processed once, instead of n times for each object.
*/
protected void processDataResults(AbstractSession session) {
this.dataResultsByPrimaryKey = new HashMap();
int size = this.dataResults.size();
Object firstKey = null;
Object lastKey = null;
List<AbstractRecord> childRows = null;
ObjectBuilder builder = getDescriptor().getObjectBuilder();
int parentIndex = getParentResultIndex();
Vector trimedFields = null;
for (int dataResultsIndex = 0; dataResultsIndex < size; dataResultsIndex++) {
AbstractRecord row = this.dataResults.get(dataResultsIndex);
AbstractRecord parentRow = row;
// Must adjust for the parent index to ensure the correct pk is extracted.
if (parentIndex > 0) {
if (trimedFields == null) { // The fields are always the same, so only build once.
trimedFields = new NonSynchronizedSubVector(row.getFields(), parentIndex, row.size());
}
Vector trimedValues = new NonSynchronizedSubVector(row.getValues(), parentIndex, row.size());
parentRow = new DatabaseRecord(trimedFields, trimedValues);
}
// Extract the primary key of the source object, to filter only the joined rows for that object.
Object sourceKey = builder.extractPrimaryKeyFromRow(parentRow, session);
// May be any outer-join so ignore null.
if (sourceKey != null) {
if (firstKey == null) {
firstKey = sourceKey;
}
if ((lastKey != null) && lastKey.equals(sourceKey)) {
childRows.add(row);
if (shouldFilterDuplicates()) {
// Also null out the row because it is a duplicate to avoid object building processing it.
this.dataResults.set(dataResultsIndex, null);
}
} else {
childRows = this.dataResultsByPrimaryKey.get(sourceKey);
if (childRows == null) {
childRows = new ArrayList();
this.dataResultsByPrimaryKey.put(sourceKey, childRows);
} else {
if (shouldFilterDuplicates()) {
// Also null out the row because it is a duplicate to avoid object building processing it.
this.dataResults.set(dataResultsIndex, null);
}
}
childRows.add(row);
lastKey = sourceKey;
}
}
}
// If pagination is used, the first and last rows may be missing their 1-m joined rows, so reject them from the results.
// This will cause them to build normally by executing a query.
if (this.isToManyJoin) {
if ((lastKey != null) && (this.baseQuery.getMaxRows() > 0)) {
this.dataResultsByPrimaryKey.remove(lastKey);
}
if ((firstKey != null) && (this.baseQuery.getFirstResult() > 0)) {
this.dataResultsByPrimaryKey.remove(firstKey);
}
}
}
/**
* Clear the data-results for joined data for a 1-m join.
*/
public void clearDataResults() {
this.dataResults = null;
this.dataResultsByPrimaryKey = null;
}
/**
* Process the data-results for joined data for a 1-m join.
* This allows incremental processing for a cursor.
*/
public AbstractRecord processDataResults(AbstractRecord row, Cursor cursor, boolean forward) {
if (this.dataResultsByPrimaryKey == null) {
this.dataResultsByPrimaryKey = new HashMap();
}
AbstractRecord parentRow = row;
List<AbstractRecord> childRows = new ArrayList<>();
childRows.add(row);
int parentIndex = getParentResultIndex();
// Must adjust for the parent index to ensure the correct pk is extracted.
Vector trimedFields = new NonSynchronizedSubVector(row.getFields(), parentIndex, row.size());
if (parentIndex > 0) {
Vector trimedValues = new NonSynchronizedSubVector(row.getValues(), parentIndex, row.size());
parentRow = new DatabaseRecord(trimedFields, trimedValues);
}
ObjectBuilder builder = getDescriptor().getObjectBuilder();
AbstractSession session = cursor.getExecutionSession();
// Extract the primary key of the source object, to filter only the joined rows for that object.
Object sourceKey = builder.extractPrimaryKeyFromRow(parentRow, session);
AbstractRecord extraRow = null;
while (true) {
AbstractRecord nextRow = null;
if (forward) {
nextRow = cursor.getAccessor().cursorRetrieveNextRow(cursor.getFields(), cursor.getResultSet(), session);
} else {
nextRow = cursor.getAccessor().cursorRetrievePreviousRow(cursor.getFields(), cursor.getResultSet(), session);
}
if (nextRow == null) {
break;
}
AbstractRecord nextParentRow = nextRow;
if (parentIndex > 0) {
Vector trimedValues = new NonSynchronizedSubVector(nextParentRow.getValues(), parentIndex, nextParentRow.size());
nextParentRow = new DatabaseRecord(trimedFields, trimedValues);
}
// Extract the primary key of the source object, to filter only the joined rows for that object.
Object nextKey = builder.extractPrimaryKeyFromRow(nextParentRow, session);
if ((sourceKey != null) && sourceKey.equals(nextKey)) {
childRows.add(nextRow);
} else {
extraRow = nextRow;
break;
}
}
this.dataResultsByPrimaryKey.put(sourceKey, childRows);
return extraRow;
}
/**
* Called to set the descriptor on a Join Managerwith in a ReportItem, durring
* initialization, and durring DatabaseQuery.checkDescriptor.
*/
public void setDescriptor(ClassDescriptor descriptor){
this.descriptor = descriptor;
}
/**
* Used for joining in conjunction with pessimistic locking.
* Iterate through a list of joined expressions and ensure expression is set on the locking
* clause for each expression that represents a pessimisically locked descriptor.
*/
public ForUpdateOfClause setupLockingClauseForJoinedExpressions(ForUpdateOfClause lockingClause, AbstractSession session) {
if (hasJoinedAttributeExpressions()){
return setupLockingClauseForJoinedExpressions(getJoinedAttributeExpressions(), session,lockingClause);
}
if (hasJoinedMappingExpressions()){
return setupLockingClauseForJoinedExpressions(getJoinedMappingExpressions(), session,lockingClause);
}
return lockingClause;
}
/**
* Used for joining in conjunction with pessimistic locking.
* Iterate through a list of joined expressions and ensure expression is set on the locking
* clause for each expression that represents a pessimisically locked descriptor.
*/
private ForUpdateOfClause setupLockingClauseForJoinedExpressions(List joinedExpressions, AbstractSession session, ForUpdateOfClause lockingClause) {
// Must iterate over all of the joined attributes, just check
// if any of them have pessimistic locking defined on the descriptor.
for (Iterator e = joinedExpressions.iterator(); e.hasNext();) {
Expression expression = (Expression)e.next();
// Expression has not yet been validated.
if (expression.isObjectExpression()) {
ObjectExpression joinedAttribute = (ObjectExpression)expression;
// Expression may not have been initialized.
joinedAttribute.getBuilder().setSession(session.getRootSession(null));
if (joinedAttribute.getBuilder().getQueryClass() == null){
joinedAttribute.getBuilder().setQueryClass(descriptor.getJavaClass());
}
ClassDescriptor nestedDescriptor = joinedAttribute.getDescriptor();
// expression may not be valid, no descriptor, validation occurs later.
if (nestedDescriptor == null) {
return lockingClause;
}
if (nestedDescriptor.hasPessimisticLockingPolicy()) {
if (lockingClause == null) {
lockingClause = new ForUpdateOfClause();
lockingClause.setLockMode(nestedDescriptor.getCMPPolicy().getPessimisticLockingPolicy().getLockingMode());
}
lockingClause.addLockedExpression(joinedAttribute);
}
}
}
return lockingClause;
}
public void setParentResultIndex(int parentsResultIndex) {
this.parentResultIndex = parentsResultIndex;
}
public int getParentResultIndex() {
return parentResultIndex;
}
public Map<DatabaseMapping, ObjectLevelReadQuery> getJoinedMappingQueryClones() {
return joinedMappingQueryClones;
}
public void setJoinedMappingQueryClones(Map joinedMappingQueryClones) {
this.joinedMappingQueryClones = joinedMappingQueryClones;
}
}