/******************************************************************************* * Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * 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.MapContainerPolicy; 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.AggregateMapping; 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 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 = (DatabaseTable)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 childrenTables = getDescriptor().getInheritancePolicy().getChildrenTables(); tablesSize = childrenTables.size(); for (int i=0; i < tablesSize; i++) { DatabaseTable table = (DatabaseTable)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 ((DataExpression)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><blockquote> * 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" * </blockquote></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 ((CollectionMapping)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<DatabaseField>(1); DatabaseField field = getField(); if (field != null) { result.add(field); } return result; } else { List<DatabaseField> result = new ArrayList<DatabaseField>(); 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 = Boolean.valueOf(queryKey.isDirectQueryKey()); } else { DatabaseMapping mapping = getMapping(); if (mapping != null) { if (mapping.isVariableOneToOneMapping()) { throw QueryException.cannotQueryAcrossAVariableOneToOneMapping(mapping, mapping.getDescriptor()); } else { isAttributeExpression = Boolean.valueOf(mapping.isDirectToFieldMapping()); } } else { isAttributeExpression = Boolean.FALSE; } } } return isAttributeExpression.booleanValue(); } @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 ((AbstractColumnMapping)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 ((MapContainerPolicy)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 = ((AggregateMapping)mapping).getReferenceDescriptor(); } else if (mapping.isForeignReferenceMapping()) { descriptor = ((ForeignReferenceMapping)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 ((CollectionMapping)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; } }