| /* |
| * 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 |
| // 11/10/2011-2.4 Guy Pelletier |
| // - 357474: Address primaryKey option from tenant discriminator column |
| package org.eclipse.persistence.internal.expressions; |
| |
| import java.io.BufferedWriter; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Enumeration; |
| 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.descriptors.FetchGroupManager; |
| 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.identitymaps.CacheId; |
| import org.eclipse.persistence.internal.queries.MappedKeyMapContainerPolicy; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.mappings.AggregateObjectMapping; |
| import org.eclipse.persistence.mappings.CollectionMapping; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.mappings.DirectCollectionMapping; |
| import org.eclipse.persistence.mappings.ForeignReferenceMapping; |
| import org.eclipse.persistence.mappings.ManyToManyMapping; |
| import org.eclipse.persistence.mappings.ObjectReferenceMapping; |
| import org.eclipse.persistence.mappings.OneToOneMapping; |
| import org.eclipse.persistence.mappings.foundation.AbstractColumnMapping; |
| import org.eclipse.persistence.mappings.querykeys.DirectQueryKey; |
| import org.eclipse.persistence.mappings.querykeys.ForeignReferenceQueryKey; |
| import org.eclipse.persistence.mappings.querykeys.QueryKey; |
| import org.eclipse.persistence.queries.DatabaseQuery; |
| import org.eclipse.persistence.queries.InMemoryQueryIndirectionPolicy; |
| import org.eclipse.persistence.queries.ReadQuery; |
| |
| /** |
| * Represents expression on query keys or mappings. |
| * This includes direct, relationships query keys and mappings. |
| */ |
| public class QueryKeyExpression extends ObjectExpression { |
| |
| /** The name of the query key. */ |
| protected String name; |
| |
| /** Cache the aliased field. Only applies to attributes. */ |
| protected DatabaseField aliasedField; |
| |
| /** Is this a query across a 1:many or many:many relationship. Does not apply to attributes. */ |
| protected boolean shouldQueryToManyRelationship; |
| |
| /** Cache the query key for performance. Store a boolean so we don't repeat the search if there isn't one. */ |
| transient protected QueryKey queryKey; |
| protected boolean hasQueryKey; |
| |
| /** Cache the mapping for performance. Store a boolean so we don't repeat the search if there isn't one. */ |
| transient protected DatabaseMapping mapping; |
| protected boolean hasMapping; |
| |
| /** PERF: Cache if the expression is an attribute expression. */ |
| protected Boolean isAttributeExpression; |
| |
| protected IndexExpression index; |
| |
| protected boolean isClonedForSubQuery = false; |
| |
| public QueryKeyExpression() { |
| this.shouldQueryToManyRelationship = false; |
| this.hasQueryKey = true; |
| this.hasMapping = true; |
| } |
| |
| public QueryKeyExpression(String aName, Expression base) { |
| super(); |
| name = aName; |
| baseExpression = base; |
| shouldUseOuterJoin = false; |
| shouldQueryToManyRelationship = false; |
| hasQueryKey = true; |
| hasMapping = true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the expression is equal to the other. |
| * This is used to allow dynamic expression's SQL to be cached. |
| */ |
| @Override |
| public boolean equals(Object object) { |
| if (this == object) { |
| return true; |
| } |
| if (!super.equals(object)) { |
| return false; |
| } |
| QueryKeyExpression expression = (QueryKeyExpression) object; |
| // Return false for anyOf expressions, as equality is unknown. |
| if (shouldQueryToManyRelationship() || expression.shouldQueryToManyRelationship()) { |
| return false; |
| } |
| return ((getName() == expression.getName()) || ((getName() != null) && getName().equals(expression.getName()))); |
| } |
| |
| /** |
| * INTERNAL: |
| * Compute a consistent hash-code for the expression. |
| * This is used to allow dynamic expression's SQL to be cached. |
| */ |
| @Override |
| public int computeHashCode() { |
| int hashCode = super.computeHashCode(); |
| if (getName() != null) { |
| hashCode = hashCode + getName().hashCode(); |
| } |
| return hashCode; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the expression to join the main table of this node to any auxiliary tables. |
| */ |
| @Override |
| public Expression additionalExpressionCriteria() { |
| if (getDescriptor() == null) { |
| return null; |
| } |
| |
| Expression criteria = getDescriptor().getQueryManager().getAdditionalJoinExpression(); |
| if (criteria != null) { |
| criteria = this.baseExpression.twist(criteria, this); |
| if (shouldUseOuterJoin() && getSession().getPlatform().shouldPrintOuterJoinInWhereClause()) { |
| criteria.convertToUseOuterJoin(); |
| } |
| } |
| if(getSession().getPlatform().shouldPrintOuterJoinInWhereClause()) { |
| if(isUsingOuterJoinForMultitableInheritance()) { |
| Expression childrenCriteria = getDescriptor().getInheritancePolicy().getChildrenJoinExpression(); |
| childrenCriteria = this.baseExpression.twist(childrenCriteria, this); |
| childrenCriteria.convertToUseOuterJoin(); |
| if(criteria == null) { |
| criteria = childrenCriteria; |
| } else { |
| criteria = criteria.and(childrenCriteria); |
| } |
| } |
| } |
| if (getDescriptor().getHistoryPolicy() != null) { |
| Expression historyCriteria = getDescriptor().getHistoryPolicy().additionalHistoryExpression(this, this); |
| if (criteria != null) { |
| criteria = criteria.and(historyCriteria); |
| } else { |
| criteria = historyCriteria; |
| } |
| } |
| 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. |
| */ |
| @Override |
| public Map additionalExpressionCriteriaMap() { |
| if (getDescriptor() == null) { |
| return null; |
| } |
| |
| HashMap tablesJoinExpressions = new HashMap(); |
| Vector<DatabaseTable> tables = getDescriptor().getTables(); |
| // skip the main table - start with i=1 |
| int tablesSize = tables.size(); |
| if (shouldUseOuterJoin() || (!getSession().getPlatform().shouldPrintInnerJoinInWhereClause())) { |
| for (int i=1; i < tablesSize; i++) { |
| DatabaseTable table = tables.elementAt(i); |
| Expression joinExpression = getDescriptor().getQueryManager().getTablesJoinExpressions().get(table); |
| joinExpression = this.baseExpression.twist(joinExpression, this); |
| if (getDescriptor().getHistoryPolicy() != null) { |
| joinExpression = joinExpression.and(getDescriptor().getHistoryPolicy().additionalHistoryExpression(this, this, i)); |
| } |
| tablesJoinExpressions.put(table, joinExpression); |
| } |
| } |
| if (isUsingOuterJoinForMultitableInheritance()) { |
| List<DatabaseTable> childrenTables = getDescriptor().getInheritancePolicy().getChildrenTables(); |
| tablesSize = childrenTables.size(); |
| for (int i=0; i < tablesSize; i++) { |
| DatabaseTable table = childrenTables.get(i); |
| Expression joinExpression = getDescriptor().getInheritancePolicy().getChildrenTablesJoinExpressions().get(table); |
| joinExpression = this.baseExpression.twist(joinExpression, this); |
| tablesJoinExpressions.put(table, joinExpression); |
| } |
| } |
| |
| return tablesJoinExpressions; |
| } |
| |
| /** |
| * INTERNAL: |
| * Find the alias for a given table |
| */ |
| @Override |
| public DatabaseTable aliasForTable(DatabaseTable table) { |
| DatabaseMapping mapping = getMapping(); |
| if (isAttribute() || ((mapping != null) && (mapping.isAggregateObjectMapping() || mapping.isTransformationMapping()))) { |
| return this.baseExpression.aliasForTable(table); |
| } |
| |
| //"ref" and "structure" mappings, no table printed in the FROM clause, need to get the table alias form the parent table |
| if ((mapping != null) && (mapping.isReferenceMapping() || mapping.isStructureMapping())) { |
| DatabaseTable alias = this.baseExpression.aliasForTable(mapping.getDescriptor().getTables().firstElement()); |
| alias.setName(alias.getName() + "." + mapping.getField().getName()); |
| return alias; |
| } |
| |
| // For direct-collection mappings the alias is store on the table expression. |
| if ((mapping != null) && (mapping.isDirectCollectionMapping())) { |
| if (tableAliases != null){ |
| DatabaseTable aliasedTable = tableAliases.keyAtValue(table); |
| if (aliasedTable != null){ |
| return aliasedTable; |
| } |
| } |
| return getTable(table).aliasForTable(table); |
| } |
| |
| return super.aliasForTable(table); |
| } |
| |
| /** |
| * ADVANCED: |
| * 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> |
| * EclipseLink: employee.get("project").treat(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 in 'where treat(t as PerformanceTireInfo).speedRating > 100' |
| QueryKeyExpression clonedExpression = new TreatAsExpression(castClass, this); |
| clonedExpression.shouldQueryToManyRelationship = this.shouldQueryToManyRelationship; |
| //using shouldUseOuterJoin to indicate the join to use between the t and PerformanceTireInfo subclass. |
| clonedExpression.hasQueryKey = this.hasQueryKey; |
| clonedExpression.hasMapping = this.hasMapping; |
| |
| this.addDerivedExpression(clonedExpression); |
| return clonedExpression; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for cloning. |
| */ |
| @Override |
| protected void postCopyIn(Map alreadyDone) { |
| super.postCopyIn(alreadyDone); |
| if (this.index != null) { |
| this.index = (IndexExpression)this.index.copiedVersionFrom(alreadyDone); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for debug printing. |
| */ |
| @Override |
| public String descriptionOfNodeType() { |
| return "Query Key"; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| public void doQueryToManyRelationship() { |
| shouldQueryToManyRelationship = true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return any additional tables that belong to this expression |
| * An example of how this method is used is to return any tables that belong to the map key |
| * when this expression traverses a mapping that uses a Map |
| */ |
| @Override |
| public List<DatabaseTable> getAdditionalTables() { |
| if (mapping != null && mapping.isCollectionMapping()){ |
| return mapping.getContainerPolicy().getAdditionalTablesForJoinQuery(); |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the field appropriately aliased |
| */ |
| @Override |
| public DatabaseField getAliasedField() { |
| if (aliasedField == null) { |
| initializeAliasedField(); |
| } |
| return aliasedField; |
| |
| } |
| |
| /** |
| * Return the alias for our table |
| */ |
| protected DatabaseTable getAliasedTable() { |
| DataExpression base = (DataExpression)this.baseExpression; |
| |
| DatabaseTable alias = base.aliasForTable(getField().getTable()); |
| if (alias == null) { |
| return getField().getTable(); |
| } else { |
| return alias; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public DatabaseField getField() { |
| if (!isAttribute()) { |
| return null; |
| } |
| QueryKey key = getQueryKeyOrNull(); |
| if ((key != null) && key.isDirectQueryKey()) { |
| return ((DirectQueryKey)key).getField(); |
| } |
| DatabaseMapping mapping = getMapping(); |
| if ((mapping == null) || mapping.getFields().isEmpty()) { |
| return null; |
| } |
| return mapping.getFields().get(0); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return all the fields |
| */ |
| @Override |
| public Vector getFields() { |
| if (isAttribute()) { |
| Vector result = new Vector(1); |
| DatabaseField field = getField(); |
| if (field != null) { |
| result.addElement(field); |
| } |
| return result; |
| } else { |
| Vector result = new Vector(); |
| result.addAll(super.getFields()); |
| if ((this.mapping != null) && this.mapping.isCollectionMapping()){ |
| List<DatabaseField> fields = this.mapping.getContainerPolicy().getAdditionalFieldsForJoin((CollectionMapping)this.mapping); |
| if (fields != null){ |
| result.addAll(fields); |
| } |
| } |
| return result; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public List<DatabaseField> getSelectionFields(ReadQuery query) { |
| if (isAttribute()) { |
| List<DatabaseField> result = new ArrayList<>(1); |
| DatabaseField field = getField(); |
| if (field != null) { |
| result.add(field); |
| } |
| return result; |
| } else { |
| List<DatabaseField> result = new ArrayList<>(); |
| result.addAll(super.getSelectionFields(query)); |
| if ((this.mapping != null) && this.mapping.isCollectionMapping()){ |
| List<DatabaseField> fields = this.mapping.getContainerPolicy().getAdditionalFieldsForJoin((CollectionMapping)this.mapping); |
| if (fields != null){ |
| result.addAll(fields); |
| } |
| } |
| return result; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Transform the object-level value into a database-level value |
| */ |
| @Override |
| public Object getFieldValue(Object objectValue, AbstractSession session) { |
| DatabaseMapping mapping = getMapping(); |
| Object fieldValue = objectValue; |
| if (mapping != null) { |
| if (mapping.isAbstractDirectMapping() || mapping.isDirectCollectionMapping()) { |
| // CR#3623207, check for IN Collection here not in mapping. |
| if (objectValue instanceof Collection) { |
| // This can actually be a collection for IN within expressions... however it would be better for expressions to handle this. |
| Collection values = (Collection)objectValue; |
| Vector fieldValues = new Vector(values.size()); |
| for (Iterator iterator = values.iterator(); iterator.hasNext();) { |
| Object value = iterator.next(); |
| if (!(value instanceof Expression)){ |
| value = getFieldValue(value, session); |
| } |
| fieldValues.add(value); |
| } |
| fieldValue = fieldValues; |
| } else { |
| if (mapping.isAbstractColumnMapping()) { |
| fieldValue = ((AbstractColumnMapping)mapping).getFieldValue(objectValue, session); |
| } else if (mapping.isDirectCollectionMapping()) { |
| fieldValue = ((DirectCollectionMapping)mapping).getFieldValue(objectValue, session); |
| } |
| } |
| } else if ((objectValue instanceof Collection) && (mapping.isForeignReferenceMapping())) { |
| // Was an IN with a collection of objects, extract their ids. |
| List ids = new ArrayList(); |
| for (Object object : ((Collection)objectValue)) { |
| if ((mapping.getReferenceDescriptor() != null) && (mapping.getReferenceDescriptor().getJavaClass().isInstance(object))) { |
| Object id = mapping.getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(object, session); |
| if (id instanceof CacheId) { |
| id = Arrays.asList(((CacheId)id).getPrimaryKey()); |
| } |
| ids.add(id); |
| } else { |
| ids.add(object); |
| } |
| } |
| fieldValue = ids; |
| } |
| } |
| |
| return fieldValue; |
| } |
| |
| @Override |
| public DatabaseMapping getMapping() { |
| if (!hasMapping) { |
| return null; |
| } |
| |
| if (mapping == null) { |
| mapping = super.getMapping(); |
| if (mapping == null) { |
| hasMapping = false; |
| } |
| } |
| return mapping; |
| } |
| |
| public DatabaseMapping getMappingFromQueryKey() { |
| QueryKey queryKey = getQueryKeyOrNull(); |
| if ((queryKey == null) || (!(queryKey instanceof DirectQueryKey))) { |
| throw QueryException.cannotConformExpression(); |
| } |
| mapping = queryKey.getDescriptor().getObjectBuilder().getMappingForField(((DirectQueryKey)queryKey).getField()); |
| if (mapping == null) { |
| throw QueryException.cannotConformExpression(); |
| } |
| return mapping; |
| } |
| |
| @Override |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns nested attribute name or null |
| */ |
| public String getNestedAttributeName() { |
| if(getMapping() != null) { |
| String attributeName = getMapping().getAttributeName(); |
| if(this.baseExpression.isExpressionBuilder()) { |
| return attributeName; |
| } else if (this.baseExpression.isQueryKeyExpression()) { |
| String nestedAttributeName = ((QueryKeyExpression)this.baseExpression).getNestedAttributeName(); |
| if(nestedAttributeName == null) { |
| return null; |
| } else { |
| return nestedAttributeName + '.' + attributeName; |
| } |
| } else { |
| return null; |
| } |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public List<DatabaseTable> getOwnedTables() { |
| if ((getMapping() != null) && getMapping().isNestedTableMapping()) { |
| List<DatabaseTable> nestedTable = null; |
| if (shouldQueryToManyRelationship()) { |
| nestedTable = new ArrayList(super.getOwnedTables()); |
| } else { |
| nestedTable = new ArrayList(1); |
| } |
| |
| nestedTable.add(new NestedTable(this)); |
| return nestedTable; |
| } |
| if ((getMapping() != null) && (getMapping().isReferenceMapping() || getMapping().isStructureMapping())) { |
| return null; |
| } |
| |
| return super.getOwnedTables(); |
| |
| } |
| |
| @Override |
| public QueryKey getQueryKeyOrNull() { |
| if (!hasQueryKey) { |
| return null; |
| } |
| |
| // Oct 19, 2000 JED |
| // Added try/catch. This was throwing a NPE in the following case |
| // expresssionBuilder.get("firstName").get("bob") |
| //moved by Gordon Yorke to cover validate and normalize |
| if (getContainingDescriptor() == null) { |
| throw QueryException.invalidQueryKeyInExpression(getName()); |
| } |
| if (queryKey == null) { |
| queryKey = getContainingDescriptor().getQueryKeyNamed(getName()); |
| if (queryKey == null) { |
| hasQueryKey = false; |
| } |
| } |
| return queryKey; |
| |
| } |
| |
| /* |
| * PUBLIC: |
| * Index method could be applied to QueryKeyExpression corresponding to CollectionMapping |
| * that has non-null listOrderField (the field holding the index values). |
| * <p>Example: |
| * <pre><blockquote> |
| * ReportQuery query = new ReportQuery(); |
| * query.setReferenceClass(Employee.class); |
| * ExpressionBuilder builder = query.getExpressionBuilder(); |
| * Expression firstNameJohn = builder.get("firstName").equal("John"); |
| * Expression anyOfProjects = builder.anyOf("projects"); |
| * Expression exp = firstNameJohn.and(anyOfProjects.index().between(2, 4)); |
| * query.setSelectionCriteria(exp); |
| * query.addAttribute("projects", anyOfProjects); |
| * |
| * SELECT DISTINCT t0.PROJ_ID, t0.PROJ_TYPE, t0.DESCRIP, t0.PROJ_NAME, t0.LEADER_ID, t0.VERSION, t1.PROJ_ID, t1.BUDGET, t1.MILESTONE |
| * FROM OL_PROJ_EMP t4, OL_SALARY t3, OL_EMPLOYEE t2, OL_LPROJECT t1, OL_PROJECT t0 |
| * WHERE ((((t2.F_NAME = 'John') AND (t4.PROJ_ORDER BETWEEN 2 AND 4)) AND (t3.OWNER_EMP_ID = t2.EMP_ID)) AND |
| * (((t4.EMP_ID = t2.EMP_ID) AND (t0.PROJ_ID = t4.PROJ_ID)) AND (t1.PROJ_ID (+) = t0.PROJ_ID))) |
| * </blockquote></pre> |
| */ |
| @Override |
| public Expression index() { |
| if(index == null) { |
| index = new IndexExpression(this); |
| } |
| return index; |
| } |
| |
| /** |
| * INTERNAL: |
| * Alias the database field for our current environment |
| */ |
| protected void initializeAliasedField() { |
| DatabaseField tempField = getField().clone(); |
| DatabaseTable aliasedTable = getAliasedTable(); |
| |
| // Put in a special check here so that if the aliasing does nothing we don't cache the |
| // result because it's invalid. This saves us from caching premature data if e.g. debugging |
| // causes us to print too early" |
| // if (aliasedTable.equals(getField().getTable())) { |
| // return; |
| // } else { |
| aliasedField = tempField; |
| aliasedField.setTable(aliasedTable); |
| // } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the expression is for a direct mapped attribute. |
| */ |
| @Override |
| public boolean isAttribute() { |
| if (isAttributeExpression == null) { |
| if (getSession() == null) { |
| // We can't tell, so say no. |
| return false; |
| } |
| QueryKey queryKey = getQueryKeyOrNull(); |
| if (queryKey != null) { |
| isAttributeExpression = queryKey.isDirectQueryKey(); |
| } else { |
| DatabaseMapping mapping = getMapping(); |
| if (mapping != null) { |
| if (mapping.isVariableOneToOneMapping()) { |
| throw QueryException.cannotQueryAcrossAVariableOneToOneMapping(mapping, mapping.getDescriptor()); |
| } else { |
| isAttributeExpression = mapping.isDirectToFieldMapping(); |
| } |
| } else { |
| isAttributeExpression = Boolean.FALSE; |
| } |
| } |
| } |
| return isAttributeExpression; |
| } |
| |
| @Override |
| public boolean isQueryKeyExpression() { |
| return true; |
| } |
| |
| /* |
| * INTERNAL: |
| * If this query key represents a foreign reference answer the |
| * base expression -> foreign reference join criteria. |
| */ |
| public Expression mappingCriteria(Expression base) { |
| Expression selectionCriteria; |
| |
| // First look for a query key, then a mapping |
| if (getQueryKeyOrNull() == null) { |
| if ((getMapping() == null) || (!getMapping().isForeignReferenceMapping())) { |
| return null; |
| } else { |
| // The join criteria is now twisted by the mappings. |
| selectionCriteria = ((ForeignReferenceMapping)getMapping()).getJoinCriteria(this, base); |
| } |
| } else { |
| if (!getQueryKeyOrNull().isForeignReferenceQueryKey()) { |
| return null; |
| } else { |
| selectionCriteria = ((ForeignReferenceQueryKey)getQueryKeyOrNull()).getJoinCriteria(); |
| selectionCriteria = this.baseExpression.twist(selectionCriteria, base); |
| } |
| } |
| |
| if (shouldUseOuterJoin() && getSession().getPlatform().shouldPrintOuterJoinInWhereClause()) { |
| selectionCriteria = selectionCriteria.convertToUseOuterJoin(); |
| } |
| |
| return selectionCriteria; |
| } |
| |
| /** |
| * INTERNAL: |
| * Normalize the expression into a printable structure. |
| * Any joins must be added to form a new root. |
| */ |
| @Override |
| public Expression normalize(ExpressionNormalizer normalizer) { |
| return normalize(normalizer, this, null); |
| } |
| |
| /** |
| * INTERNAL: |
| * Check if new expression need to be created for sub queries and re-normalized. |
| */ |
| protected Expression checkJoinForSubSelectWithParent(ExpressionNormalizer normalizer, Expression base, List<Expression> foreignKeyJoinPointer) { |
| SQLSelectStatement statement = normalizer.getStatement(); |
| if(!isClonedForSubQuery && statement.isSubSelect() && statement.getParentStatement().getBuilder().equals(getBuilder())) { |
| if (baseExpression.isQueryKeyExpression()) { |
| QueryKeyExpression baseQueryKeyExpression = (QueryKeyExpression) baseExpression; |
| DatabaseMapping mapping = baseQueryKeyExpression.getMapping(); |
| if (mapping != null && mapping.isOneToOneMapping()) { |
| if (statement.getOptimizedClonedExpressions().containsKey(this)) { |
| return statement.getOptimizedClonedExpressions().get(this); |
| } |
| |
| QueryKeyExpression clonedBaseExpression = null; |
| if (baseQueryKeyExpression.hasBeenNormalized()) { |
| // Call normalize again to get same expression. |
| clonedBaseExpression = (QueryKeyExpression) baseQueryKeyExpression.normalize(normalizer); |
| } |
| |
| if (clonedBaseExpression == null && baseQueryKeyExpression.getBaseExpression().isQueryKeyExpression()) { |
| DatabaseMapping basebaseExprMapping = ((QueryKeyExpression)baseQueryKeyExpression.getBaseExpression()).getMapping(); |
| if (basebaseExprMapping != null && basebaseExprMapping.isOneToOneMapping()) { |
| // Let base expression normalization re-create base base expression and normalize it if needed. |
| clonedBaseExpression = (QueryKeyExpression) baseQueryKeyExpression.normalize(normalizer); |
| } |
| } |
| |
| if (clonedBaseExpression == null) { |
| // Clone base expression & normalize |
| clonedBaseExpression = new QueryKeyExpression(baseQueryKeyExpression.getName(), baseQueryKeyExpression.getBaseExpression()); |
| clonedBaseExpression.shouldQueryToManyRelationship = baseQueryKeyExpression.shouldQueryToManyRelationship; |
| clonedBaseExpression.shouldUseOuterJoin = baseQueryKeyExpression.shouldUseOuterJoin; |
| clonedBaseExpression.hasQueryKey = baseQueryKeyExpression.hasQueryKey; |
| clonedBaseExpression.hasMapping = baseQueryKeyExpression.hasMapping; |
| clonedBaseExpression.isAttributeExpression = baseQueryKeyExpression.isAttributeExpression; |
| clonedBaseExpression.isClonedForSubQuery = true; |
| |
| clonedBaseExpression = (QueryKeyExpression) clonedBaseExpression.normalize(normalizer); |
| statement.addOptimizedClonedExpressions(baseQueryKeyExpression, clonedBaseExpression); |
| } |
| |
| // Clone expression, normalize & return. |
| QueryKeyExpression clonedExpression = new QueryKeyExpression(name, clonedBaseExpression); |
| clonedExpression.shouldQueryToManyRelationship = this.shouldQueryToManyRelationship; |
| clonedExpression.shouldUseOuterJoin = this.shouldUseOuterJoin; |
| clonedExpression.hasQueryKey = this.hasQueryKey; |
| clonedExpression.hasMapping = this.hasMapping; |
| clonedExpression.isAttributeExpression = this.isAttributeExpression; |
| clonedExpression.isClonedForSubQuery = true; |
| |
| if (base == this) { |
| clonedExpression = (QueryKeyExpression) clonedExpression.normalize(normalizer, clonedExpression, foreignKeyJoinPointer); |
| } else { |
| // Caller invoked overloaded method with different base, RelationExpression in this case. |
| clonedExpression = (QueryKeyExpression) clonedExpression.normalize(normalizer, base, foreignKeyJoinPointer); |
| } |
| statement.addOptimizedClonedExpressions(this, clonedExpression); |
| return clonedExpression; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * For CR#2456 if this is part of an objExp.equal(objExp), do not need to add |
| * additional expressions to normalizer both times, and the foreign key join |
| * replaces the equal expression. |
| */ |
| public Expression normalize(ExpressionNormalizer normalizer, Expression base, List<Expression> foreignKeyJoinPointer) { |
| if (this.hasBeenNormalized) { |
| return this; |
| } |
| // Bug 397619 - It should only be normalized by parent. |
| // If subselect & not normalized, always clone and normalize |
| // if it has parent builder. |
| Expression clonedExpression = checkJoinForSubSelectWithParent(normalizer, base, foreignKeyJoinPointer); |
| if (clonedExpression != null) { |
| return clonedExpression; |
| } |
| super.normalize(normalizer); |
| DatabaseMapping mapping = getMapping(); |
| SQLSelectStatement statement = normalizer.getStatement(); |
| if ((mapping != null) && mapping.isDirectToXMLTypeMapping()) { |
| statement.setRequiresAliases(true); |
| } |
| |
| // Check if any joins need to be added. |
| if (isAttribute()) { |
| return this; |
| } |
| |
| ReadQuery query = normalizer.getStatement().getQuery(); |
| // Record any class used in a join to invalidate query results cache. |
| if ((query != null) && query.shouldCacheQueryResults()) { |
| if ((mapping != null) && (mapping.getReferenceDescriptor() != null) && (mapping.getReferenceDescriptor().getJavaClass() != null)) { |
| query.getQueryResultsCachePolicy().getInvalidationClasses().add(mapping.getReferenceDescriptor().getJavaClass()); |
| } else { |
| QueryKey queryKey = getQueryKeyOrNull(); |
| if ((queryKey != null) && queryKey.isForeignReferenceQueryKey()) { |
| query.getQueryResultsCachePolicy().getInvalidationClasses().add(((ForeignReferenceQueryKey)queryKey).getReferenceClass()); |
| } |
| } |
| } |
| |
| // If the mapping is 'ref' or 'structure', no join needed. |
| if ((mapping != null) && (mapping.isReferenceMapping() || mapping.isStructureMapping())) { |
| statement.setRequiresAliases(true); |
| return this; |
| } |
| |
| // Compute if a distinct is required during normalization. |
| if (shouldQueryToManyRelationship() && (!statement.isDistinctComputed()) && (!statement.isAggregateSelect())) { |
| statement.useDistinct(); |
| } |
| |
| // Turn off DISTINCT if nestedTableMapping is used (not supported by Oracle 8.1.5). |
| if ((mapping != null) && mapping.isNestedTableMapping()) { |
| // There are two types of nested tables, one used by clients, one used by mappings, do nothing in the mapping case. |
| if (!shouldQueryToManyRelationship()) { |
| return this; |
| } |
| statement.dontUseDistinct(); |
| } |
| |
| // Normalize the ON clause if present. Need to use rebuild, not twist as parameters are real parameters. |
| if (this.onClause != null) { |
| this.onClause = this.onClause.normalize(normalizer); |
| } |
| |
| Expression mappingExpression = mappingCriteria(base); |
| if (mappingExpression != null) { |
| mappingExpression = mappingExpression.normalize(normalizer); |
| } |
| if (mappingExpression != null) { |
| // If the join was an outer join we must not add the join criteria to the where clause, |
| // if the platform prints the join in the from clause. |
| if (shouldUseOuterJoin() && (getSession().getPlatform().isInformixOuterJoin())) { |
| setOuterJoinExpIndex(statement.addOuterJoinExpressionsHolders(this, mappingExpression, null, null)); |
| normalizer.addAdditionalExpression(mappingExpression.and(additionalExpressionCriteria())); |
| return this; |
| } else if ((shouldUseOuterJoin() && (!getSession().getPlatform().shouldPrintOuterJoinInWhereClause())) |
| || (!getSession().getPlatform().shouldPrintInnerJoinInWhereClause())) { |
| setOuterJoinExpIndex(statement.addOuterJoinExpressionsHolders(this, mappingExpression, additionalExpressionCriteriaMap(), null)); |
| if ((getDescriptor() != null) && (getDescriptor().getHistoryPolicy() != null)) { |
| Expression historyOnClause = getDescriptor().getHistoryPolicy().additionalHistoryExpression(this, this, 0); |
| if (getOnClause() != null) { |
| setOnClause(getOnClause().and(historyOnClause)); |
| } else { |
| setOnClause(historyOnClause); |
| } |
| } |
| return this; |
| } else if (isUsingOuterJoinForMultitableInheritance() && (!getSession().getPlatform().shouldPrintOuterJoinInWhereClause())) { |
| setOuterJoinExpIndex(statement.addOuterJoinExpressionsHolders(null, null, additionalExpressionCriteriaMap(), mapping.getReferenceDescriptor())); |
| // fall through to the main case |
| } |
| |
| // This must be added even if outer. Actually it should be converted to use a right outer join, but that gets complex |
| // so we do not support this current which is a limitation in some cases. |
| if (foreignKeyJoinPointer != null) { |
| // If this expression is right side of an objExp.equal(objExp), one |
| // need not add additionalExpressionCriteria twice. |
| // Also the join will replace the original objExp.equal(objExp). |
| // For CR#2456. |
| foreignKeyJoinPointer.add(mappingExpression.and(this.onClause)); |
| } else { |
| normalizer.addAdditionalExpression(mappingExpression.and(additionalExpressionCriteria()).and(this.onClause)); |
| } |
| } |
| // For bug 2900974 special code for DirectCollectionMappings moved to printSQL. |
| return this; |
| } |
| |
| /** |
| * INTERNAL: |
| * Print SQL onto the stream, using the ExpressionPrinter for context |
| */ |
| @Override |
| public void printSQL(ExpressionSQLPrinter printer) { |
| if (isAttribute()) { |
| printer.printField(getAliasedField()); |
| } |
| |
| // If the mapping is a direct collection then this falls into a gray area. |
| // It must be treated as an attribute at this moment for it has a direct field. |
| // However it is not an attribute in the sense that it also represents a foreign |
| // reference and a mapping criteria has been added. |
| // For bug 2900974 these are now handled as non-attributes during normalize but |
| // as attributes when printing SQL. |
| // |
| if ((!isAttribute()) && (getMapping() != null) && getMapping().isDirectCollectionMapping()) { |
| DirectCollectionMapping directCollectionMapping = (DirectCollectionMapping)getMapping(); |
| |
| // The aliased table comes for free as it was a required part of the join criteria. |
| TableExpression table = (TableExpression)getTable(directCollectionMapping.getReferenceTable()); |
| DatabaseTable aliasedTable = table.aliasForTable(table.getTable()); |
| DatabaseField aliasedField = directCollectionMapping.getDirectField().clone(); |
| aliasedField.setTable(aliasedTable); |
| printer.printField(aliasedField); |
| } |
| |
| if ((getMapping() != null) && getMapping().isNestedTableMapping()) { |
| DatabaseTable tableAlias = aliasForTable(new NestedTable(this)); |
| printer.printString(tableAlias.getName()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Print java for project class generation |
| */ |
| @Override |
| public void printJava(ExpressionJavaPrinter printer) { |
| this.baseExpression.printJava(printer); |
| if (!shouldUseOuterJoin()) { |
| if (!shouldQueryToManyRelationship()) { |
| printer.printString(".get("); |
| } else { |
| printer.printString(".anyOf("); |
| } |
| } else { |
| if (!shouldQueryToManyRelationship()) { |
| printer.printString(".getAllowingNull("); |
| } else { |
| printer.printString(".anyOfAllowingNone("); |
| } |
| } |
| printer.printString("\"" + getName() + "\")"); |
| } |
| |
| /** |
| * INTERNAL: |
| * This expression is built on a different base than the one we want. Rebuild it and |
| * return the root of the new tree |
| */ |
| @Override |
| public Expression rebuildOn(Expression newBase) { |
| Expression newLocalBase = this.baseExpression.rebuildOn(newBase); |
| QueryKeyExpression result = null; |
| |
| // For bug 3096634 rebuild outer joins correctly from the start. |
| if (shouldUseOuterJoin) { |
| result = (QueryKeyExpression)newLocalBase.getAllowingNull(getName()); |
| } else { |
| result = (QueryKeyExpression)newLocalBase.get(getName()); |
| } |
| if (shouldQueryToManyRelationship) { |
| result.doQueryToManyRelationship(); |
| } |
| result.setSelectIfOrderedBy(selectIfOrderedBy()); |
| if (castClass != null){ |
| result.setCastClass(castClass); |
| } |
| return result; |
| } |
| |
| /** |
| * INTERNAL: |
| * A special version of rebuildOn where the newBase need not be a new |
| * ExpressionBuilder but any expression. |
| * <p> |
| * For nested joined attributes, the joined attribute query must have |
| * its joined attributes rebuilt relative to it. |
| */ |
| public Expression rebuildOn(Expression oldBase, Expression newBase) { |
| if (this == oldBase) { |
| return newBase; |
| } |
| Expression newLocalBase = ((QueryKeyExpression)this.baseExpression).rebuildOn(oldBase, newBase); |
| QueryKeyExpression result = null; |
| |
| // For bug 3096634 rebuild outer joins correctly from the start. |
| if (shouldUseOuterJoin) { |
| result = (QueryKeyExpression)newLocalBase.getAllowingNull(getName()); |
| } else { |
| result = (QueryKeyExpression)newLocalBase.get(getName()); |
| } |
| if (shouldQueryToManyRelationship) { |
| result.doQueryToManyRelationship(); |
| } |
| result.setSelectIfOrderedBy(selectIfOrderedBy()); |
| return result; |
| } |
| |
| /** |
| * Reset cached information here so that we can be sure we're accurate. |
| */ |
| @Override |
| protected void resetCache() { |
| hasMapping = true; |
| mapping = null; |
| hasQueryKey = true; |
| queryKey = null; |
| } |
| |
| public boolean shouldQueryToManyRelationship() { |
| return shouldQueryToManyRelationship; |
| } |
| |
| /** |
| * INTERNAL: |
| * Rebuild myself against the base, with the values of parameters supplied by the context |
| * expression. This is used for transforming a standalone expression (e.g. the join criteria of a mapping) |
| * into part of some larger expression. You normally would not call this directly, instead calling twist |
| * See the comment there for more details" |
| */ |
| @Override |
| public Expression twistedForBaseAndContext(Expression newBase, Expression context, Expression oldBase) { |
| if (oldBase == null || this.baseExpression == oldBase) { |
| Expression twistedBase = this.baseExpression.twistedForBaseAndContext(newBase, context, oldBase); |
| QueryKeyExpression result = (QueryKeyExpression)twistedBase.get(getName()); |
| if (shouldUseOuterJoin) { |
| result.doUseOuterJoin(); |
| } |
| if (shouldQueryToManyRelationship) { |
| result.doQueryToManyRelationship(); |
| } |
| return result; |
| } |
| |
| return this; |
| } |
| |
| /** |
| * Do any required validation for this node. Throw an exception if it's incorrect. |
| */ |
| @Override |
| public void validateNode() { |
| QueryKey queryKey = getQueryKeyOrNull(); |
| DatabaseMapping mapping = getMapping(); |
| |
| if ((queryKey == null) && (mapping == null)) { |
| throw QueryException.invalidQueryKeyInExpression(getName()); |
| } |
| |
| |
| Object theOneThatsNotNull = null; |
| boolean qkIsToMany = false; |
| if (queryKey != null) { |
| theOneThatsNotNull = queryKey; |
| qkIsToMany = queryKey.isManyToManyQueryKey() || queryKey.isOneToManyQueryKey(); |
| } |
| boolean isNestedMapping = false; |
| if (mapping != null) { |
| // Bug 2847621 - Add Aggregate Collection to the list of valid items for outer join. |
| if (this.shouldUseOuterJoin && (!(mapping.isOneToOneMapping() || mapping.isOneToManyMapping() || mapping.isManyToManyMapping() || mapping.isAggregateCollectionMapping() || mapping.isDirectCollectionMapping()))) { |
| throw QueryException.outerJoinIsOnlyValidForOneToOneMappings(mapping); |
| } |
| qkIsToMany = mapping.isCollectionMapping(); |
| if (this.index != null) { |
| if (qkIsToMany) { |
| CollectionMapping collectionMapping = (CollectionMapping)mapping; |
| if(collectionMapping.getListOrderField() != null) { |
| this.index.setField(collectionMapping.getListOrderField()); |
| addDerivedField(this.index); |
| } else { |
| throw QueryException.indexRequiresCollectionMappingWithListOrderField(this, collectionMapping); |
| } |
| } else { |
| throw QueryException.indexRequiresCollectionMappingWithListOrderField(this, mapping); |
| } |
| } |
| isNestedMapping = mapping.isNestedTableMapping(); |
| theOneThatsNotNull = mapping; |
| } else { |
| if (this.index != null) { |
| throw QueryException.indexRequiresCollectionMappingWithListOrderField(this, null); |
| } |
| } |
| if ((!shouldQueryToManyRelationship()) && qkIsToMany && (!isNestedMapping)) { |
| throw QueryException.invalidUseOfToManyQueryKeyInExpression(theOneThatsNotNull); |
| } |
| if (shouldQueryToManyRelationship() && !qkIsToMany) { |
| throw QueryException.invalidUseOfAnyOfInExpression(theOneThatsNotNull); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the value for in memory comparison. |
| * This is only valid for valueable expressions. |
| */ |
| @Override |
| public Object valueFromObject(Object object, AbstractSession session, AbstractRecord translationRow, int valueHolderPolicy, boolean isObjectUnregistered) { |
| // The expression may be across a relationship, in which case it must be traversed. |
| if ((!this.baseExpression.isExpressionBuilder()) && this.baseExpression.isQueryKeyExpression()) { |
| object = this.baseExpression.valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered); |
| |
| // toDo: Null means the join filters out the row, returning null is not correct if an inner join, |
| // outer/inner joins need to be fixed to filter correctly. |
| if (object == null) { |
| return null; |
| } |
| |
| // If from an anyof the object will be a collection of values, |
| // A new vector must union the object values and the values extracted from it. |
| if (object instanceof Vector) { |
| Vector comparisonVector = new Vector(((Vector)object).size() + 2); |
| for (Enumeration valuesToIterate = ((Vector)object).elements(); |
| valuesToIterate.hasMoreElements();) { |
| Object vectorObject = valuesToIterate.nextElement(); |
| if (vectorObject == null) { |
| comparisonVector.addElement(null); |
| } else { |
| Object valueOrValues = valuesFromCollection(vectorObject, session, valueHolderPolicy, isObjectUnregistered); |
| |
| // If a collection of values were extracted union them. |
| if (valueOrValues instanceof Vector) { |
| for (Enumeration nestedValuesToIterate = ((Vector)valueOrValues).elements(); |
| nestedValuesToIterate.hasMoreElements();) { |
| comparisonVector.addElement(nestedValuesToIterate.nextElement()); |
| } |
| } else { |
| comparisonVector.addElement(valueOrValues); |
| } |
| } |
| } |
| return comparisonVector; |
| } |
| } |
| return valuesFromCollection(object, session, valueHolderPolicy, isObjectUnregistered); |
| } |
| |
| /** |
| * INTERNAL |
| * This method iterates through a collection and gets the values from the objects to conform in an in-memory query. |
| */ |
| public Object valuesFromCollection(Object object, AbstractSession session, int valueHolderPolicy, boolean isObjectUnregistered) { |
| // in case the mapping is null - this can happen if a query key is being used |
| // In this case, check for the query key and find it's mapping. |
| boolean readMappingFromQueryKey = false; |
| if (getMapping() == null) { |
| getMappingFromQueryKey(); |
| readMappingFromQueryKey = true; |
| } |
| |
| // For bug 2780817 get the mapping directly from the object. In EJB 2.0 |
| // inheritance, each child must override mappings defined in an abstract |
| // class with its own. |
| DatabaseMapping mapping = this.mapping; |
| ClassDescriptor descriptor = mapping.getDescriptor(); |
| if (descriptor.hasInheritance() && (descriptor.getJavaClass() != object.getClass())) { |
| descriptor = descriptor.getInheritancePolicy().getDescriptor(object.getClass()); |
| mapping = descriptor.getObjectBuilder().getMappingForAttributeName(mapping.getAttributeName()); |
| } |
| |
| //fetch group support |
| if (descriptor.hasFetchGroupManager()) { |
| FetchGroupManager fetchGroupManager = descriptor.getFetchGroupManager(); |
| if (fetchGroupManager.isPartialObject(object) && (!fetchGroupManager.isAttributeFetched(object, mapping.getAttributeName()))) { |
| //the conforming attribute is not fetched, simply throw exception |
| throw QueryException.cannotConformUnfetchedAttribute(mapping.getAttributeName()); |
| } |
| } |
| |
| if (mapping.isAbstractColumnMapping()) { |
| return mapping.valueFromObject(object, mapping.getField(), session); |
| } else if (mapping.isForeignReferenceMapping()) { |
| //CR 3677 integration of a ValueHolderPolicy |
| Object valueFromMapping = mapping.getAttributeValueFromObject(object); |
| if (!((ForeignReferenceMapping)mapping).getIndirectionPolicy().objectIsInstantiated(valueFromMapping)) { |
| if (valueHolderPolicy != InMemoryQueryIndirectionPolicy.SHOULD_TRIGGER_INDIRECTION) { |
| //If the client wishes us to trigger the indirection then we should do so, |
| //Other wise throw the exception |
| throw QueryException.mustInstantiateValueholders();// you should instantiate the valueholder for this to work |
| } |
| |
| // maybe we should throw this exception from the start, to save time |
| } |
| Object valueToIterate = mapping.getRealAttributeValueFromObject(object, session); |
| UnitOfWorkImpl uow = isObjectUnregistered ? (UnitOfWorkImpl)session : null; |
| |
| // First check that object in fact is unregistered. |
| // toDo: ?? Why is this commented out? Why are we supporting the unregistered thing at all? |
| // Does not seem to be any public API for this, nor every used internally? |
| //if (isObjectUnregistered) { |
| // isObjectUnregistered = !uow.getCloneMapping().containsKey(object); |
| //} |
| if (mapping.isCollectionMapping() && (valueToIterate != null)) { |
| // For bug 2766379 must use the correct version of vectorFor to |
| // unwrap the result same time. |
| valueToIterate = mapping.getContainerPolicy().vectorFor(valueToIterate, session); |
| |
| // toDo: If the value is empty, need to support correct inner/outer join filtering symantics. |
| // For CR 2612601, try to partially replace the result with already |
| // registered objects. |
| if (isObjectUnregistered && (uow.getCloneMapping().get(object) == null)) { |
| Vector objectValues = (Vector)valueToIterate; |
| for (int i = 0; i < objectValues.size(); i++) { |
| Object original = objectValues.elementAt(i); |
| Object clone = uow.getIdentityMapAccessorInstance().getIdentityMapManager().getFromIdentityMap(original); |
| if (clone != null) { |
| objectValues.setElementAt(clone, i); |
| } |
| } |
| } |
| |
| // For CR 2612601, conforming without registering, a query could be |
| // bob.get("address").get("city").equal("Ottawa"); where the address |
| // has been registered and modified in the UOW, but bob has not. Thus |
| // even though bob does not point to the modified address now, it will |
| // as soon as it is registered, so should point to it here. |
| } else if (isObjectUnregistered && (uow.getCloneMapping().get(object) == null)) { |
| Object clone = uow.getIdentityMapAccessorInstance().getIdentityMapManager().getFromIdentityMap(valueToIterate); |
| if (clone != null) { |
| valueToIterate = clone; |
| } |
| } |
| return valueToIterate; |
| } else if (mapping.isAggregateMapping()) { |
| Object aggregateValue = mapping.getAttributeValueFromObject(object); |
| // Bug 3995468 - if this query key is to a mapping in an aggregate object, get the object from actual mapping rather than the aggregate mapping |
| while (readMappingFromQueryKey && mapping.isAggregateObjectMapping() && !((AggregateObjectMapping)mapping).getReferenceClass().equals(queryKey.getDescriptor().getJavaClass())) { |
| mapping = mapping.getReferenceDescriptor().getObjectBuilder().getMappingForField(((DirectQueryKey)queryKey).getField()); |
| aggregateValue = mapping.getRealAttributeValueFromObject(aggregateValue, session); |
| } |
| return aggregateValue; |
| } else { |
| throw QueryException.cannotConformExpression(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Lookup the descriptor for this item by traversing its expression recursively. |
| */ |
| @Override |
| public ClassDescriptor getLeafDescriptor(DatabaseQuery query, ClassDescriptor rootDescriptor, AbstractSession session) { |
| Expression baseExpression = getBaseExpression(); |
| ClassDescriptor baseDescriptor = baseExpression.getLeafDescriptor(query, rootDescriptor, session); |
| |
| if (isMapEntryExpression()) { |
| // get the expression that owns the mapping for the table entry |
| Expression owningExpression = ((QueryKeyExpression)baseExpression).getBaseExpression(); |
| ClassDescriptor owningDescriptor = owningExpression.getLeafDescriptor(query, rootDescriptor, session); |
| |
| // Get the mapping that owns the table |
| CollectionMapping mapping = (CollectionMapping)owningDescriptor.getObjectBuilder().getMappingForAttributeName(baseExpression.getName()); |
| |
| return mapping.getContainerPolicy().getDescriptorForMapKey(); |
| } |
| |
| ClassDescriptor descriptor = null; |
| String attributeName = getName(); |
| |
| DatabaseMapping mapping = baseDescriptor.getObjectBuilder().getMappingForAttributeName(attributeName); |
| |
| if (mapping == null) { |
| QueryKey queryKey = baseDescriptor.getQueryKeyNamed(attributeName); |
| if (queryKey != null) { |
| if (queryKey.isForeignReferenceQueryKey()) { |
| descriptor = session.getDescriptor(((ForeignReferenceQueryKey)queryKey).getReferenceClass()); |
| } else { // if (queryKey.isDirectQueryKey()) |
| descriptor = queryKey.getDescriptor(); |
| } |
| } |
| if (descriptor == null) { |
| throw QueryException.invalidExpressionForQueryItem(this, query); |
| } |
| } else if (mapping.isAggregateMapping()) { |
| descriptor = mapping.getReferenceDescriptor(); |
| } else if (mapping.isForeignReferenceMapping()) { |
| descriptor = mapping.getReferenceDescriptor(); |
| } |
| return descriptor; |
| } |
| |
| /** |
| * INTERNAL: |
| * Lookup the mapping for this item by traversing its expression recursively. |
| * If an aggregate of foreign mapping is found it is traversed. |
| */ |
| @Override |
| public DatabaseMapping getLeafMapping(DatabaseQuery query, ClassDescriptor rootDescriptor, AbstractSession session) { |
| if (isMapEntryExpression()){ |
| MapEntryExpression mapEntryExpression = (MapEntryExpression)this; |
| |
| // get the expression that we want the table entry for |
| QueryKeyExpression baseExpression = (QueryKeyExpression)mapEntryExpression.getBaseExpression(); |
| |
| // get the expression that owns the mapping for the table entry |
| Expression owningExpression = baseExpression.getBaseExpression(); |
| ClassDescriptor owningDescriptor = owningExpression.getLeafDescriptor(query, rootDescriptor, session); |
| |
| // Get the mapping that owns the table |
| CollectionMapping mapping = (CollectionMapping)owningDescriptor.getObjectBuilder().getMappingForAttributeName(baseExpression.getName()); |
| |
| if (mapEntryExpression.shouldReturnMapEntry()) { |
| return mapping; |
| } |
| if (mapping.getContainerPolicy().isMappedKeyMapPolicy()){ |
| MappedKeyMapContainerPolicy policy = (MappedKeyMapContainerPolicy)mapping.getContainerPolicy(); |
| return (DatabaseMapping)policy.getKeyMapping(); |
| } |
| return mapping; |
| } |
| |
| Expression baseExpression = getBaseExpression(); |
| |
| ClassDescriptor descriptor = baseExpression.getLeafDescriptor(query, rootDescriptor, session); |
| if (descriptor == null){ |
| return null; |
| } |
| return descriptor.getObjectBuilder().getMappingForAttributeName(getName()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to print a debug form of the expression tree. |
| */ |
| @Override |
| public void writeDescriptionOn(BufferedWriter writer) throws IOException { |
| writer.write(getName()); |
| if (castClass != null){ |
| writer.write(" (" + castClass.getName() + ") "); |
| } |
| writer.write(tableAliasesDescription()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether this expression corresponds to DirectCollection. |
| */ |
| @Override |
| public boolean isDirectCollection() { |
| if(getMapping() != null) { |
| return getMapping().isDirectCollectionMapping(); |
| } else { |
| if(getQueryKeyOrNull() != null) { |
| return this.queryKey.isDirectCollectionQueryKey(); |
| } else { |
| return false; |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether this expression corresponds to OneToOne. |
| */ |
| public boolean isOneToOne() { |
| if(getMapping() != null) { |
| return getMapping().isOneToOneMapping(); |
| } else { |
| if(getQueryKeyOrNull() != null) { |
| return this.queryKey.isOneToOneQueryKey(); |
| } else { |
| return false; |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether this expression corresponds to OneToMany. |
| */ |
| public boolean isOneToMany() { |
| if(getMapping() != null) { |
| return getMapping().isOneToManyMapping(); |
| } else { |
| if(getQueryKeyOrNull() != null) { |
| return this.queryKey.isOneToManyQueryKey(); |
| } else { |
| return false; |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether this expression corresponds to ManyToMany. |
| */ |
| public boolean isManyToMany() { |
| if(getMapping() != null) { |
| return getMapping().isManyToManyMapping(); |
| } else { |
| if(getQueryKeyOrNull() != null) { |
| return this.queryKey.isManyToManyQueryKey(); |
| } else { |
| return false; |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the expression if for a map key mapping where the key is a OneToOne. |
| */ |
| public boolean isMapKeyObjectRelationship() { |
| if (getMapping() != null) { |
| return getMapping().isCollectionMapping() && ((CollectionMapping)getMapping()).isMapKeyObjectRelationship(); |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if descriptor for the map key mapping where the key is a OneToOne. |
| */ |
| public ClassDescriptor getMapKeyDescriptor() { |
| return getMapping().getContainerPolicy().getDescriptorForMapKey(); |
| } |
| |
| /** |
| * Calculate the reference table for based on the various QueryKeyExpression |
| * usages (join query keys, custom defined query keys, or query keys for |
| * mappings). |
| * |
| * Called from {@link SQLSelectStatement#appendFromClauseForOuterJoin}. |
| * |
| * @return DatabaseTable |
| */ |
| public DatabaseTable getReferenceTable() { |
| if(getMapping() != null) { |
| if (getMapping().isDirectCollectionMapping()) { |
| return ((DirectCollectionMapping)getMapping()).getReferenceTable(); |
| } else { |
| return getMapping().getReferenceDescriptor().getTables().firstElement(); |
| } |
| } else { |
| return ((ForeignReferenceQueryKey)getQueryKeyOrNull()).getReferenceTable(getDescriptor()); |
| } |
| } |
| |
| /** |
| * Calculate the source table for based on the various QueryKeyExpression |
| * usages (join query keys, custom defined query keys, or query keys for |
| * mappings). |
| * |
| * Called from {@link SQLSelectStatement#appendFromClauseForOuterJoin}. |
| * |
| * @return DatabaseTable |
| */ |
| public DatabaseTable getSourceTable() { |
| if (getBaseExpression().isExpressionBuilder() && getBuilder().hasViewTable()) { |
| return getBuilder().getViewTable(); |
| } |
| if (getMapping() != null) { |
| // Grab the source table from the mapping not just the first table |
| // from the descriptor. In an joined inheritance hierarchy, the |
| // fk used in the outer join may be from a subclasses's table. |
| if (getMapping().isObjectReferenceMapping() && ((ObjectReferenceMapping) getMapping()).isForeignKeyRelationship()) { |
| return getMapping().getFields().firstElement().getTable(); |
| } else { |
| return ((ObjectExpression)this.baseExpression).getDescriptor().getTables().firstElement(); |
| } |
| } else { |
| return ((ForeignReferenceQueryKey)getQueryKeyOrNull()).getSourceTable(); |
| } |
| } |
| |
| /** |
| * Calculate the relation table for based on the various QueryKeyExpression |
| * usages (join query keys, custom defined query keys, or query keys for |
| * mappings). |
| * |
| * Called from {@link SQLSelectStatement#appendFromClauseForOuterJoin}. |
| * |
| * @return DatabaseTable |
| */ |
| @Override |
| public DatabaseTable getRelationTable() { |
| if(getMapping() != null) { |
| if(getMapping().isManyToManyMapping()) { |
| return ((ManyToManyMapping)getMapping()).getRelationTable(); |
| } else if(getMapping().isOneToOneMapping()) { |
| return ((OneToOneMapping)getMapping()).getRelationTable(); |
| } |
| } else { |
| if(getQueryKeyOrNull().isForeignReferenceQueryKey()) { |
| return ((ForeignReferenceQueryKey)getQueryKeyOrNull()).getRelationTable(getDescriptor()); |
| } |
| } |
| return null; |
| } |
| } |