/******************************************************************************* | |
* Copyright (c) 1998, 2014 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.util.*; | |
import org.eclipse.persistence.exceptions.*; | |
import org.eclipse.persistence.mappings.*; | |
import org.eclipse.persistence.mappings.foundation.AbstractColumnMapping; | |
import org.eclipse.persistence.queries.*; | |
import org.eclipse.persistence.internal.helper.*; | |
import org.eclipse.persistence.expressions.*; | |
import org.eclipse.persistence.internal.sessions.AbstractRecord; | |
import org.eclipse.persistence.internal.sessions.AbstractSession; | |
import org.eclipse.persistence.descriptors.ClassDescriptor; | |
/** | |
* <p><b>Purpose</b>:Used for all relation operators except for between. | |
*/ | |
public class RelationExpression extends CompoundExpression { | |
/** PERF: Cache if the expression is an object comparison expression. */ | |
protected Boolean isObjectComparisonExpression; | |
public RelationExpression() { | |
super(); | |
} | |
/** | |
* Test that both of our children are field nodes | |
*/ | |
protected boolean allChildrenAreFields() { | |
return (this.firstChild.getFields().size() == 1) && (this.secondChild.getFields().size() == 1); | |
} | |
/** | |
* INTERNAL: | |
* Modify this individual expression node to use outer joins wherever there are | |
* equality operations between two field nodes. | |
*/ | |
protected void convertNodeToUseOuterJoin() { | |
if ((this.operator.getSelector() == ExpressionOperator.Equal) && allChildrenAreFields()) { | |
setOperator(getOperator(ExpressionOperator.EqualOuterJoin)); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Used for debug printing. | |
*/ | |
public String descriptionOfNodeType() { | |
return "Relation"; | |
} | |
/** | |
* INTERNAL: | |
* Check if the object conforms to the expression in memory. | |
* This is used for in-memory querying. | |
* If the expression in not able to determine if the object conform throw a not supported exception. | |
*/ | |
public boolean doesConform(Object object, AbstractSession session, AbstractRecord translationRow, int valueHolderPolicy, boolean isObjectUnregistered) { | |
if ((this.secondChild.getBuilder().getSession() == null) || (this.firstChild.getBuilder().getSession() == null)) { | |
// Parallel selects are not supported in memory. | |
throw QueryException.cannotConformExpression(); | |
} | |
// Extract the value from the right side. | |
//CR 3677 integration of valueHolderPolicy | |
Object rightValue = | |
this.secondChild.valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered); | |
// Extract the value from the object. | |
//CR 3677 integration of valueHolderPolicy | |
Object leftValue = | |
this.firstChild.valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered); | |
// The right value may be a Collection of values from an anyof, or an in. | |
if (rightValue instanceof Collection) { | |
// Vector may mean anyOf, or an IN. | |
// CR#3240862, code for IN was incorrect, and was check for between which is a function not a relation. | |
// Must check for IN and NOTIN, currently object comparison is not supported. | |
// IN must be handled separately because right is always a vector of values, vector never means anyof. | |
if ((this.operator.getSelector() == ExpressionOperator.In) || | |
(this.operator.getSelector() == ExpressionOperator.NotIn)) { | |
if (isObjectComparison(session)) { | |
// In object comparisons are not currently supported, in-memory or database. | |
throw QueryException.cannotConformExpression(); | |
} else { | |
// Left may be single value or anyof vector. | |
if (leftValue instanceof Vector) { | |
return doesAnyOfLeftValuesConform((Vector)leftValue, rightValue, session); | |
} else { | |
return this.operator.doesRelationConform(leftValue, rightValue); | |
} | |
} | |
} | |
// Otherwise right vector means an anyof on right, so must check each value. | |
for (Enumeration rightEnum = ((Vector)rightValue).elements(); rightEnum.hasMoreElements(); ) { | |
Object tempRight = rightEnum.nextElement(); | |
// Left may also be an anyof some must check each left with each right. | |
if (leftValue instanceof Vector) { | |
// If anyof the left match return true, otherwise keep checking. | |
if (doesAnyOfLeftValuesConform((Vector)leftValue, tempRight, session)) { | |
return true; | |
} | |
} | |
if (doValuesConform(leftValue, tempRight, session)) { | |
return true; | |
} | |
} | |
// None of the value conform. | |
return false; | |
} | |
// Otherwise the left may also be a vector of values from an anyof. | |
if (leftValue instanceof Vector) { | |
return doesAnyOfLeftValuesConform((Vector)leftValue, rightValue, session); | |
} | |
// Otherwise it is a simple value to value comparison, or simple object to object comparison. | |
return doValuesConform(leftValue, rightValue, session); | |
} | |
/** | |
* Conform in-memory the collection of left values with the right value for this expression. | |
* This is used for anyOf support when the left side is a collection of values. | |
*/ | |
protected boolean doesAnyOfLeftValuesConform(Vector leftValues, Object rightValue, AbstractSession session) { | |
// Check each left value with the right value. | |
for (int index = 0; index < leftValues.size(); index++) { | |
Object leftValue = leftValues.get(index); | |
if (doValuesConform(leftValue, rightValue, session)) { | |
// Return true if any value matches. | |
return true; | |
} | |
} | |
// Return false only if none of the values match. | |
return false; | |
} | |
/** | |
* Conform in-memory the two values. | |
*/ | |
protected boolean doValuesConform(Object leftValue, Object rightValue, AbstractSession session) { | |
// Check for object comparison. | |
if (isObjectComparison(session)) { | |
return doesObjectConform(leftValue, rightValue, session); | |
} else { | |
return this.operator.doesRelationConform(leftValue, rightValue); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Check if the object conforms to the expression in memory. | |
* This is used for in-memory querying across object relationships. | |
*/ | |
public boolean doesObjectConform(Object leftValue, Object rightValue, AbstractSession session) { | |
if ((leftValue == null) && (rightValue == null)) { | |
return performSelector(true); | |
} | |
if ((leftValue == null) || (rightValue == null)) { | |
//both are not null. | |
return performSelector(false); | |
} | |
Class javaClass = leftValue.getClass(); | |
if (javaClass != rightValue.getClass()) { | |
return performSelector(false); | |
} | |
ClassDescriptor descriptor = session.getDescriptor(javaClass); | |
// Currently cannot conform aggregate comparisons in-memory. | |
if (descriptor.isAggregateDescriptor()) { | |
throw QueryException.cannotConformExpression(); | |
} | |
Object leftPrimaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(leftValue, session); | |
Object rightPrimaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(rightValue, session); | |
return performSelector(leftPrimaryKey.equals(rightPrimaryKey)); | |
} | |
/** | |
* INTERNAL: | |
* Extract the values from the expression into the row. | |
* Ensure that the query is querying the exact primary key. | |
* @param requireExactMatch refers to the primary key extracted gaurenteeing the result, | |
* if not exact it is a heuristic and the cache hit will be conformed to the expression after the lookup | |
* Return false if not on the primary key. | |
*/ | |
@Override | |
public boolean extractValues(boolean primaryKeyOnly, boolean requireExactMatch, ClassDescriptor descriptor, AbstractRecord primaryKeyRow, AbstractRecord translationRow) { | |
// If an exact match is required then the operator must be equality. | |
if (requireExactMatch && (!(this.operator.getSelector() == ExpressionOperator.Equal))) { | |
return false; | |
} | |
// If not an exact match only =, <, <=, >=, >,... are allowed but not IN which has a different type | |
if ((!requireExactMatch) && (this.operator.getSelector() == ExpressionOperator.In)) { | |
return false; | |
} | |
DatabaseField field = null; | |
Object value = null; | |
if (this.secondChild.isConstantExpression()) { | |
value = ((ConstantExpression)this.secondChild).getValue(); | |
} else if (this.secondChild.isParameterExpression() && (translationRow != null)) { | |
value = translationRow.get(((ParameterExpression)this.secondChild).getField()); | |
} else if (this.firstChild.isConstantExpression()) { | |
value = ((ConstantExpression)this.firstChild).getValue(); | |
} else if (this.firstChild.isParameterExpression() && (translationRow != null)) { | |
value = translationRow.get(((ParameterExpression)this.firstChild).getField()); | |
} | |
if (value == null) { | |
return false; | |
} | |
// Descriptor to use for child query key | |
ClassDescriptor descriptorForChild = null; | |
// Ensure that the primary key is being queried on. | |
if (this.firstChild.isFieldExpression()) { | |
FieldExpression child = (FieldExpression)this.firstChild; | |
// Only get value for the source object. | |
if (!child.getBaseExpression().isExpressionBuilder()) { | |
return false; | |
} | |
field = child.getField(); | |
} else if (this.firstChild.isQueryKeyExpression()) { | |
QueryKeyExpression child = (QueryKeyExpression)this.firstChild; | |
// Only get value for the source object. | |
if (!child.getBaseExpression().isExpressionBuilder()) { | |
return false; | |
} | |
descriptorForChild = ((ExpressionBuilder)child.getBaseExpression()).getDescriptor(); | |
if (descriptorForChild == null) { | |
descriptorForChild = descriptor; | |
} | |
DatabaseMapping mapping = descriptorForChild.getObjectBuilder().getMappingForAttributeName(child.getName()); | |
if (mapping != null) { | |
if (primaryKeyOnly && !mapping.isPrimaryKeyMapping()) { | |
return false; | |
} | |
// Only support referencing limited number of relationship types. | |
if (mapping.isObjectReferenceMapping() || mapping.isAggregateObjectMapping()) { | |
mapping.writeFromAttributeIntoRow(value, primaryKeyRow, getSession()); | |
return true; | |
} | |
if (!mapping.isAbstractColumnMapping()) { | |
return false; | |
} | |
field = ((AbstractColumnMapping)mapping).getField(); | |
} else { | |
// Only get field for the source object. | |
field = descriptorForChild.getObjectBuilder().getFieldForQueryKeyName(child.getName()); | |
} | |
} else if (this.secondChild.isFieldExpression()) { | |
FieldExpression child = (FieldExpression)this.secondChild; | |
// Only get field for the source object. | |
if (!child.getBaseExpression().isExpressionBuilder()) { | |
return false; | |
} | |
field = child.getField(); | |
} else if (this.secondChild.isQueryKeyExpression()) { | |
QueryKeyExpression child = (QueryKeyExpression)this.secondChild; | |
// Only get value for the source object. | |
if (!child.getBaseExpression().isExpressionBuilder()) { | |
return false; | |
} | |
descriptorForChild = ((ExpressionBuilder)child.getBaseExpression()).getDescriptor(); | |
if (descriptorForChild == null) { | |
descriptorForChild = descriptor; | |
} | |
DatabaseMapping mapping = descriptorForChild.getObjectBuilder().getMappingForAttributeName(child.getName()); | |
// Only support referencing limited number of relationship types. | |
if (mapping != null) { | |
if (primaryKeyOnly && !mapping.isPrimaryKeyMapping()) { | |
return false; | |
} | |
if (mapping.isObjectReferenceMapping() || mapping.isAggregateObjectMapping()) { | |
mapping.writeFromAttributeIntoRow(value, primaryKeyRow, getSession()); | |
return true; | |
} | |
if (!mapping.isAbstractColumnMapping()) { | |
return false; | |
} | |
field = ((AbstractColumnMapping)mapping).getField(); | |
} else { | |
field = descriptorForChild.getObjectBuilder().getFieldForQueryKeyName(child.getName()); | |
} | |
} else { | |
return false; | |
} | |
if (field == null) { | |
return false; | |
} | |
// Check child descriptor's primary key fields if the passed descriptor does not contain the field | |
if (primaryKeyOnly && !descriptor.getPrimaryKeyFields().contains(field)) { | |
if (descriptorForChild != null && descriptorForChild != descriptor && descriptorForChild.getPrimaryKeyFields().contains(field)) { | |
// Child descriptor's pk fields contains the field, return true. | |
// Do not add the field from the query key's descriptor to the primaryKeyRow | |
return true; | |
} else { | |
return false; | |
} | |
} | |
// Do not replace the field in the row with the same field | |
if (primaryKeyRow.get(field) != null) { | |
return false; | |
} | |
primaryKeyRow.put(field, value); | |
return true; | |
} | |
/** | |
* INTERNAL: | |
* Return if the expression is not a valid primary key expression and add all primary key fields to the set. | |
*/ | |
@Override | |
public boolean extractFields(boolean requireExactMatch, boolean primaryKey, ClassDescriptor descriptor, List<DatabaseField> searchFields, Set<DatabaseField> foundFields) { | |
// If an exact match is required then the operator must be equality. | |
if (requireExactMatch && (!(this.operator.getSelector() == ExpressionOperator.Equal))) { | |
return false; | |
} | |
// If not an exact match only =, <, <=, >=, >,... are allowed but not IN which has a different type | |
if ((!requireExactMatch) && (this.operator.getSelector() == ExpressionOperator.In)) { | |
return false; | |
} | |
DatabaseField field = null; | |
if (!(this.secondChild.isConstantExpression() || this.secondChild.isParameterExpression()) | |
&& !(this.firstChild.isConstantExpression() || (this.firstChild.isParameterExpression()))) { | |
return false; | |
} | |
// Ensure that the primary key is being queried on. | |
if (this.firstChild.isFieldExpression()) { | |
FieldExpression child = (FieldExpression)this.firstChild; | |
// Only get value for the source object. | |
if (!child.getBaseExpression().isExpressionBuilder()) { | |
return false; | |
} | |
field = child.getField(); | |
} else if (this.firstChild.isQueryKeyExpression()) { | |
QueryKeyExpression child = (QueryKeyExpression)this.firstChild; | |
// Only get value for the source object. | |
if (!child.getBaseExpression().isExpressionBuilder()) { | |
return false; | |
} | |
DatabaseMapping mapping = descriptor.getObjectBuilder().getMappingForAttributeName(child.getName()); | |
if (mapping != null) { | |
if (primaryKey && !mapping.isPrimaryKeyMapping()) { | |
return false; | |
} | |
// Only support referencing limited number of relationship types. | |
if (mapping.isObjectReferenceMapping() || mapping.isAggregateObjectMapping()) { | |
for (DatabaseField mappingField : mapping.getFields()) { | |
if (searchFields.contains(mappingField)) { | |
foundFields.add(mappingField); | |
} | |
} | |
return true; | |
} | |
if (!mapping.isAbstractColumnMapping()) { | |
return false; | |
} | |
field = ((AbstractColumnMapping)mapping).getField(); | |
} else { | |
// Only get field for the source object. | |
field = descriptor.getObjectBuilder().getFieldForQueryKeyName(child.getName()); | |
} | |
} else if (this.secondChild.isFieldExpression()) { | |
FieldExpression child = (FieldExpression)this.secondChild; | |
// Only get field for the source object. | |
if (!child.getBaseExpression().isExpressionBuilder()) { | |
return false; | |
} | |
field = child.getField(); | |
} else if (this.secondChild.isQueryKeyExpression()) { | |
QueryKeyExpression child = (QueryKeyExpression)this.secondChild; | |
// Only get value for the source object. | |
if (!child.getBaseExpression().isExpressionBuilder()) { | |
return false; | |
} | |
DatabaseMapping mapping = descriptor.getObjectBuilder().getMappingForAttributeName(child.getName()); | |
// Only support referencing limited number of relationship types. | |
if (mapping != null) { | |
if (!mapping.isPrimaryKeyMapping()) { | |
return false; | |
} | |
if (mapping.isObjectReferenceMapping() || mapping.isAggregateObjectMapping()) { | |
for (DatabaseField mappingField : mapping.getFields()) { | |
if (searchFields.contains(mappingField)) { | |
foundFields.add(mappingField); | |
} | |
} | |
return true; | |
} | |
if (!mapping.isAbstractColumnMapping()) { | |
return false; | |
} | |
field = ((AbstractColumnMapping)mapping).getField(); | |
} else { | |
field = descriptor.getObjectBuilder().getFieldForQueryKeyName(child.getName()); | |
} | |
} else { | |
return false; | |
} | |
if ((field == null) || (!searchFields.contains(field))) { | |
return false; | |
} | |
foundFields.add(field); | |
return true; | |
} | |
/** | |
* Check if the expression is an equal null expression, these must be handle in a special way in SQL. | |
*/ | |
public boolean isEqualNull(ExpressionSQLPrinter printer) { | |
if (isObjectComparison(printer.getSession())) { | |
return false; | |
} else if (this.operator.getSelector() != ExpressionOperator.Equal) { | |
return false; | |
} else if (this.secondChild.isConstantExpression() && (((ConstantExpression)this.secondChild).getValue() == null)) { | |
return true; | |
} else if (this.secondChild.isParameterExpression() && (printer.getTranslationRow() != null) && | |
(((ParameterExpression)this.secondChild).getValue(printer.getTranslationRow(), printer.getSession()) == null)) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/** | |
* Check if the expression is an equal null expression, these must be handle in a special way in SQL. | |
*/ | |
public boolean isNotEqualNull(ExpressionSQLPrinter printer) { | |
if (isObjectComparison(printer.getSession())) { | |
return false; | |
} else if (this.operator.getSelector() != ExpressionOperator.NotEqual) { | |
return false; | |
} else if (this.secondChild.isConstantExpression() && (((ConstantExpression)this.secondChild).getValue() == null)) { | |
return true; | |
} else if (this.secondChild.isParameterExpression() && (printer.getTranslationRow() != null) && | |
(((ParameterExpression)this.secondChild).getValue(printer.getTranslationRow(), printer.getSession()) == null)) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/** | |
* INTERNAL: | |
* Return if the represents an object comparison. | |
*/ | |
protected boolean isObjectComparison(AbstractSession session) { | |
if (this.isObjectComparisonExpression == null) { | |
// PERF: direct-access. | |
// Base must have a session set in its builder to call getMapping, isAttribute | |
if (this.firstChild.getBuilder().getSession() == null) { | |
this.firstChild.getBuilder().setSession(session.getRootSession(null)); | |
} | |
if (this.secondChild.getBuilder().getSession() == null) { | |
this.secondChild.getBuilder().setSession(session.getRootSession(null)); | |
} | |
if ((!this.firstChild.isObjectExpression()) || ((ObjectExpression)this.firstChild).isAttribute()) { | |
if ((this.secondChild.isObjectExpression()) && !((ObjectExpression)this.secondChild).isAttribute()) { | |
DatabaseMapping mapping = ((ObjectExpression)this.secondChild).getMapping(); | |
if ((mapping != null) && (mapping.isDirectCollectionMapping()) && !(this.secondChild.isMapEntryExpression())) { | |
this.isObjectComparisonExpression = Boolean.FALSE; | |
} else { | |
this.isObjectComparisonExpression = Boolean.valueOf(this.firstChild.isObjectExpression() | |
|| this.firstChild.isValueExpression() | |
|| this.firstChild.isSubSelectExpression() | |
|| (this.firstChild.isFunctionExpression() && ((FunctionExpression)this.firstChild).operator.isAnyOrAll())); | |
} | |
} else { | |
this.isObjectComparisonExpression = Boolean.FALSE; | |
} | |
} else { | |
DatabaseMapping mapping = ((ObjectExpression)this.firstChild).getMapping(); | |
if ((mapping != null) && (mapping.isDirectCollectionMapping()) && !(this.firstChild.isMapEntryExpression())) { | |
this.isObjectComparisonExpression = Boolean.FALSE; | |
} else { | |
this.isObjectComparisonExpression = Boolean.valueOf(this.secondChild.isObjectExpression() | |
|| this.secondChild.isValueExpression() | |
|| this.secondChild.isSubSelectExpression() | |
|| (this.secondChild.isFunctionExpression() && ((FunctionExpression)this.secondChild).operator.isAnyOrAll())); | |
} | |
} | |
} | |
return this.isObjectComparisonExpression.booleanValue(); | |
} | |
/** | |
* INTERNAL: | |
*/ | |
public boolean isRelationExpression() { | |
return true; | |
} | |
/** | |
* PERF: Optimize out unnecessary joins. | |
* Check for relation based on foreign keys, i.e. emp.address.id = :id, and avoid join. | |
* @return null if cannot be optimized, otherwise the optimized normalized expression. | |
*/ | |
protected Expression checkForeignKeyJoinOptimization(Expression first, Expression second, ExpressionNormalizer normalizer) { | |
if (first.isQueryKeyExpression() | |
&& (((QueryKeyExpression)first).getBaseExpression() != null) | |
&& ((QueryKeyExpression)first).getBaseExpression().isQueryKeyExpression()) { | |
// Do not optimize for subselect if it is using parent builder, and needs to clone. | |
if(normalizer.getStatement().isSubSelect() && normalizer.getStatement().getParentStatement().getBuilder().equals(first.getBuilder())) { | |
return null; | |
} | |
QueryKeyExpression mappingExpression = (QueryKeyExpression)((QueryKeyExpression)first).getBaseExpression(); | |
if ((mappingExpression.getBaseExpression() != null) | |
&& mappingExpression.getBaseExpression().isObjectExpression() | |
&& (!mappingExpression.shouldUseOuterJoin())) { | |
// Must ensure it has been normalized first. | |
mappingExpression.getBaseExpression().normalize(normalizer); | |
DatabaseMapping mapping = mappingExpression.getMapping(); | |
if ((mapping != null) && mapping.isOneToOneMapping() | |
&& (!((OneToOneMapping)mapping).hasCustomSelectionQuery()) | |
&& ((OneToOneMapping)mapping).isForeignKeyRelationship() | |
&& (second.isConstantExpression() || second.isParameterExpression())) { | |
DatabaseField targetField = ((QueryKeyExpression)first).getField(); | |
DatabaseField sourceField = ((OneToOneMapping)mapping).getTargetToSourceKeyFields().get(targetField); | |
if (sourceField != null) { | |
Expression optimizedExpression = this.operator.expressionFor(mappingExpression.getBaseExpression().getField(sourceField), second); | |
// Ensure the base still applies the correct conversion. | |
second.setLocalBase(first); | |
return optimizedExpression.normalize(normalizer); | |
} | |
} | |
} | |
} | |
return null; | |
} | |
/** | |
* INTERNAL: | |
* Check for object comparison as this requires for the expression to be replaced by the object comparison. | |
*/ | |
public Expression normalize(ExpressionNormalizer normalizer) { | |
// PERF: Optimize out unnecessary joins. | |
Expression optimizedExpression = checkForeignKeyJoinOptimization(this.firstChild, this.secondChild, normalizer); | |
if (optimizedExpression == null) { | |
optimizedExpression = checkForeignKeyJoinOptimization(this.secondChild, this.firstChild, normalizer); | |
} | |
if (optimizedExpression != null) { | |
return optimizedExpression; | |
} | |
if (!isObjectComparison(normalizer.getSession())) { | |
return super.normalize(normalizer); | |
} else { | |
//bug # 2956674 | |
//validation is moved into normalize to ensure that expressions are valid before we attempt to work with them | |
// super.normalize will call validateNode as well. | |
validateNode(); | |
} | |
if ((this.operator.getSelector() != ExpressionOperator.Equal) && | |
(this.operator.getSelector() != ExpressionOperator.NotEqual) && | |
(this.operator.getSelector() != ExpressionOperator.In) && | |
(this.operator.getSelector() != ExpressionOperator.NotIn)) { | |
throw QueryException.invalidOperatorForObjectComparison(this); | |
} | |
// Check for IN with objects, "object IN :objects", "objects IN (:object1, :object2 ...)", "object IN aList" | |
if ((this.operator.getSelector() == ExpressionOperator.In) || (this.operator.getSelector() == ExpressionOperator.NotIn)) { | |
// Switch object comparison to compare on primary key. | |
Expression left = this.firstChild; | |
if (!left.isObjectExpression()) { | |
throw QueryException.invalidExpression(this); | |
} | |
// Check if the left is for a 1-1 mapping, then optimize to compare on foreign key to avoid join. | |
DatabaseMapping mapping = null; | |
if (left.isQueryKeyExpression()) { | |
((ObjectExpression)left).getBaseExpression().normalize(normalizer); | |
mapping = ((ObjectExpression)left).getMapping(); | |
} | |
ClassDescriptor descriptor = null; | |
List<DatabaseField> sourceFields = null; | |
List<DatabaseField> targetFields = null; | |
if ((mapping != null) && mapping.isOneToOneMapping() | |
&& (!((OneToOneMapping)mapping).hasRelationTableMechanism()) | |
&& (!((OneToOneMapping)mapping).hasCustomSelectionQuery())) { | |
left = ((ObjectExpression)left).getBaseExpression(); | |
descriptor = mapping.getReferenceDescriptor(); | |
left = left.normalize(normalizer); | |
Map<DatabaseField, DatabaseField> targetToSourceKeyFields = ((OneToOneMapping)mapping).getTargetToSourceKeyFields(); | |
sourceFields = new ArrayList(targetToSourceKeyFields.size()); | |
targetFields = new ArrayList(targetToSourceKeyFields.size()); | |
for (Map.Entry<DatabaseField, DatabaseField> entry : targetToSourceKeyFields.entrySet()) { | |
sourceFields.add(entry.getValue()); | |
targetFields.add(entry.getKey()); | |
} | |
} else { | |
mapping = null; | |
left = left.normalize(normalizer); | |
descriptor = ((ObjectExpression)left).getDescriptor(); | |
sourceFields = descriptor.getPrimaryKeyFields(); | |
targetFields = sourceFields; | |
} | |
boolean composite = sourceFields.size() > 1; | |
DatabaseField sourceField = sourceFields.get(0); | |
DatabaseField targetField = targetFields.get(0); | |
Expression newLeft = null; | |
if (composite) { | |
// For composite ids an array comparison is used, this only works on some databases. | |
List fieldExpressions = new ArrayList(); | |
for (DatabaseField field : sourceFields) { | |
fieldExpressions.add(left.getField(field)); | |
} | |
newLeft = getBuilder().value(sourceFields); | |
} else { | |
newLeft = left.getField(sourceField); | |
} | |
setFirstChild(newLeft); | |
Expression right = this.secondChild; | |
if (right.isConstantExpression()) { | |
// Check for a constant with a List of objects, need to collect the ids (also allow a list of ids). | |
ConstantExpression constant = (ConstantExpression)right; | |
if (constant.getValue() instanceof Collection) { | |
Collection objects = (Collection)constant.getValue(); | |
List newObjects = new ArrayList(objects.size()); | |
for (Object object : objects) { | |
if (object instanceof Expression) { | |
if (composite) { | |
// For composite ids an array comparison is used, this only works on some databases. | |
List values = new ArrayList(); | |
for (DatabaseField field : targetFields) { | |
values.add(((Expression)object).getField(field)); | |
} | |
object = getBuilder().value(values); | |
} else { | |
object = ((Expression)object).getField(targetField); | |
} | |
} else if (descriptor.getJavaClass().isInstance(object)) { | |
if (composite) { | |
// For composite ids an array comparison is used, this only works on some databases. | |
List values = new ArrayList(); | |
for (DatabaseField field : targetFields) { | |
values.add(descriptor.getObjectBuilder().extractValueFromObjectForField(object, field, normalizer.getSession())); | |
} | |
object = getBuilder().value(values); | |
} else { | |
object = descriptor.getObjectBuilder().extractValueFromObjectForField(object, targetField, normalizer.getSession()); | |
} | |
} else { | |
// Assume it is an id, so leave it. | |
} | |
newObjects.add(object); | |
} | |
constant.setValue(newObjects); | |
} else { | |
throw QueryException.invalidExpression(this); | |
} | |
} else if (right.isParameterExpression()) { | |
// Parameters must be handled when the call is executed. | |
} else { | |
throw QueryException.invalidExpression(this); | |
} | |
return super.normalize(normalizer); | |
} | |
ObjectExpression first = null; | |
Expression second = null; | |
// One side must be an object expression. | |
if (this.firstChild.isObjectExpression()) { | |
first = (ObjectExpression)this.firstChild; | |
second = this.secondChild; | |
} else { | |
first = (ObjectExpression)this.secondChild; | |
second = this.firstChild; | |
} | |
// Check for sub-selects, "object = ALL(Select object...) or ANY(Select object...), or "object = (Select object..)" | |
if (second.isFunctionExpression()) { | |
FunctionExpression funcExp = (FunctionExpression)second; | |
if (funcExp.operator.isAnyOrAll()) { | |
SubSelectExpression subSelectExp = (SubSelectExpression)funcExp.getChildren().get(1); | |
ReportQuery subQuery = subSelectExp.getSubQuery(); | |
// some db (derby) require that in EXIST(SELECT...) subquery returns a single column | |
subQuery.getItems().clear(); | |
subQuery.addItem("one", new ConstantExpression(Integer.valueOf(1), subQuery.getExpressionBuilder())); | |
Expression subSelectCriteria = subQuery.getSelectionCriteria(); | |
ExpressionBuilder subBuilder = subQuery.getExpressionBuilder(); | |
ExpressionBuilder builder = first.getBuilder(); | |
Expression newExp; | |
if (funcExp.operator.isAny()) { | |
// Any or Some | |
if (this.operator.getSelector() == ExpressionOperator.Equal) { | |
subSelectCriteria = subBuilder.equal(first).and(subSelectCriteria); | |
} else { | |
subSelectCriteria = subBuilder.notEqual(first).and(subSelectCriteria); | |
} | |
subQuery.setSelectionCriteria(subSelectCriteria); | |
newExp = builder.exists(subQuery); | |
} else { | |
// All | |
if (this.operator.getSelector() == ExpressionOperator.Equal) { | |
subSelectCriteria = subBuilder.notEqual(first).and(subSelectCriteria); | |
} else { | |
subSelectCriteria = subBuilder.equal(first).and(subSelectCriteria); | |
} | |
subQuery.setSelectionCriteria(subSelectCriteria); | |
newExp = builder.notExists(subQuery); | |
} | |
return newExp.normalize(normalizer); | |
} | |
} else if (second.isSubSelectExpression()) { | |
SubSelectExpression subSelectExp = (SubSelectExpression)second; | |
ReportQuery subQuery = subSelectExp.getSubQuery(); | |
// some db (derby) require that in EXIST(SELECT...) subquery returns a single column | |
subQuery.getItems().clear(); | |
subQuery.addItem("one", new ConstantExpression(Integer.valueOf(1), subQuery.getExpressionBuilder())); | |
Expression subSelectCriteria = subQuery.getSelectionCriteria(); | |
ExpressionBuilder subBuilder = subQuery.getExpressionBuilder(); | |
ExpressionBuilder builder = first.getBuilder(); | |
Expression newExp; | |
// Any or Some | |
if (this.operator.getSelector() == ExpressionOperator.Equal) { | |
subSelectCriteria = subBuilder.equal(first).and(subSelectCriteria); | |
} else { | |
subSelectCriteria = subBuilder.notEqual(first).and(subSelectCriteria); | |
} | |
subQuery.setSelectionCriteria(subSelectCriteria); | |
newExp = builder.exists(subQuery); | |
return newExp.normalize(normalizer); | |
} | |
// This can either be comparison to another object, null or another expression reference. | |
// Object comparisons can be done on other object builders, 1:1 or 1:m m:m mappings, | |
// 1:m/m:m must twist the primary key expression, | |
// 1:1 must not join into the target but become the foreign key expression. | |
// The value may be a constant or another expression. | |
Expression foreignKeyJoin = null; | |
// OPTIMIZATION 1: IDENTITY for CR#2456 / bug 2778339 | |
// Most exists subqueries have something like projBuilder.equal(empBuilder.anyOf("projects")) | |
// to correlate the subquery to the enclosing query. | |
// TopLink can produce SQL with one less join by not mapping projBuilder and | |
// anyOf("projects") to separate tables and equating them, but by treating | |
// them as one and the same thing: as identical. | |
// This trick can be pulled off by giving both the same TableAliasLookup, | |
// but needs to be done very conservatively. | |
// the equal() will be replaced directly with the mappingCriteria() of the anyOf("projects") | |
// Example. emp.equal(emp.get("manager")) will now produce this SQL: | |
// SELECT ... FROM EMPLOYEE t0 WHERE (t0.EMP_ID = t0.MANAGER_ID) not: | |
// SELECT ... FROM EMPLOYEE t0, EMPLOYEE t1 WHERE ((t0.EMP_ID = t1.EMP_ID) | |
// AND (t0.MANAGER_ID = t1.EMP_ID)) | |
if // If setting two query keys to equal the user probably intends a proper join. | |
//.equal(anyOf() or get()) | |
(first.isExpressionBuilder() && second.isQueryKeyExpression() | |
&& (!((QueryKeyExpression)second).hasDerivedExpressions()) // The right side is not used for anything else. | |
&& normalizer.getSession().getPlatform().shouldPrintInnerJoinInWhereClause()) { | |
first = (ExpressionBuilder)first.normalize(normalizer); | |
// If FK joins go in the WHERE clause, want to get hold of it and | |
// not put it in normalizer.additionalExpressions. | |
List<Expression> foreignKeyJoinPointer = new ArrayList(1); | |
QueryKeyExpression queryKey = (QueryKeyExpression)second; | |
// If inside an OR the foreign key join must be on both sides. | |
if (queryKey.hasBeenNormalized()) { | |
queryKey.setHasBeenNormalized(false); | |
} | |
queryKey = (QueryKeyExpression)queryKey.normalize(normalizer, first, foreignKeyJoinPointer); | |
if (!foreignKeyJoinPointer.isEmpty()) { | |
foreignKeyJoin = foreignKeyJoinPointer.get(0); | |
// Will make left and right identical in the SQL. | |
if (first.getTableAliases() == null) { | |
TableAliasLookup tableAliases = new TableAliasLookup(); | |
first.setTableAliases(tableAliases); | |
queryKey.setTableAliases(tableAliases); | |
} else { | |
queryKey.setTableAliases(first.getTableAliases()); | |
} | |
} | |
} | |
// OPTIMIZATION 2: for 1-1 mappings and get(...).equal(null) | |
// Imagine you had addr1 = emp.get("address"); then addr1.equal(addr2); | |
// One could go (addr1.ADDRESS_ID = addr2.ADDRESS_ID) and (emp.ADDR_ID = addr1.ADDRESS_ID) (foreign key join). | |
// The optimization is to drop addr1 and instead have: (emp.ADDR_ID = addr2.ADDRESS_ID). | |
// Since emp can have only 1 address (OneToOne) the addr1.equal(addr2) is | |
// implicit. This way if addr1 is used only for the comparison it can | |
// be optimized out. | |
// Also if addr2 were NULL there must be no join, just (emp.ADDR_ID = NULL) | |
// For bug 3105559 handle AggregateObject case (emp.get("period").equal(period2) | |
// which always falls into this case. | |
else if // For bug 2718460, some QueryKeyExpressions have a query key but no mapping. | |
// An example is the "back-ref" query key for batch reads. Must not | |
// attempt the optimization for these. | |
(!first.isExpressionBuilder() && !((QueryKeyExpression)first).shouldQueryToManyRelationship() && | |
(((QueryKeyExpression)first).getMapping() != null)) { | |
// Normalize firstChild's base only, as firstChild will be optimized out. | |
if (first.getBaseExpression() != null) { | |
first.setBaseExpression(first.getBaseExpression().normalize(normalizer)); | |
} | |
if (second.isConstantExpression()) { | |
Object targetObject = ((ConstantExpression)second).getValue(); | |
foreignKeyJoin = first.getMapping().buildObjectJoinExpression(first, targetObject, getSession()); | |
} else if (second.isObjectExpression() || second.isParameterExpression()) { | |
foreignKeyJoin = first.getMapping().buildObjectJoinExpression(first, second, getSession()); | |
} else { | |
throw QueryException.invalidUseOfToManyQueryKeyInExpression(this); | |
} | |
} | |
// DEFAULT: Left and right are separate entities, and the | |
// equal() will be replaced with a comparison by primary key. | |
if (foreignKeyJoin == null) { | |
first = (ObjectExpression)first.normalize(normalizer); | |
// A ConstantExpression stores a selection object. Compare the primary | |
// keys of the first expression and the selection object. | |
if (second.isConstantExpression()) { | |
Object value = ((ConstantExpression)second).getValue(); | |
Expression keyExpression = | |
first.getDescriptor().getObjectBuilder().buildPrimaryKeyExpressionFromObject(value, getSession()); | |
foreignKeyJoin = first.twist(keyExpression, first); | |
// Each expression will represent a separate table, so compare the primary | |
// keys of the first and second expressions. | |
} else if (second.isObjectExpression() || second.isParameterExpression()) { | |
foreignKeyJoin = | |
first.twist(first.getDescriptor().getObjectBuilder().getPrimaryKeyExpression(), second); | |
} else { | |
throw QueryException.invalidUseOfToManyQueryKeyInExpression(this); | |
} | |
} | |
if (this.operator.getSelector() == ExpressionOperator.NotEqual) { | |
foreignKeyJoin = foreignKeyJoin.not(); | |
} | |
return foreignKeyJoin.normalize(normalizer); | |
} | |
/** | |
* INTERNAL: | |
* Check if the object conforms to the expression in memory. | |
* This is used for in-memory querying across object relationships. | |
*/ | |
public boolean performSelector(boolean areValuesEqual) { | |
if (this.operator.getSelector() == ExpressionOperator.Equal) { | |
return areValuesEqual; | |
} else if (this.operator.getSelector() == ExpressionOperator.NotEqual) { | |
return !areValuesEqual; | |
} else { | |
throw QueryException.cannotConformExpression(); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Print SQL | |
*/ | |
public void printSQL(ExpressionSQLPrinter printer) { | |
// If both sides are parameters, some databases don't allow binding. | |
if (printer.getPlatform().isDynamicSQLRequiredForFunctions() | |
&& ((this.firstChild.isParameterExpression() || this.firstChild.isConstantExpression()) | |
&& (this.secondChild.isParameterExpression() || this.secondChild.isConstantExpression()))) { | |
printer.getCall().setUsesBinding(false); | |
} | |
if (isEqualNull(printer)) { | |
this.firstChild.isNull().printSQL(printer); | |
} else if (isNotEqualNull(printer)) { | |
this.firstChild.notNull().printSQL(printer); | |
} else { | |
super.printSQL(printer); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Print java for project class generation | |
*/ | |
public void printJava(ExpressionJavaPrinter printer) { | |
ExpressionOperator realOperator = getPlatformOperator(printer.getPlatform()); | |
Expression tempFirstChild = this.firstChild; | |
Expression tempSecondChild = this.secondChild; | |
realOperator.printJavaDuo(tempFirstChild, tempSecondChild, printer); | |
} | |
/** | |
* INTERNAL: | |
* Print SQL without adding parentheses (for DB2 outer joins). | |
*/ | |
public void printSQLNoParens(ExpressionSQLPrinter printer) { | |
ExpressionOperator realOperator = getPlatformOperator(printer.getPlatform()); | |
realOperator.printDuo(this.firstChild, this.secondChild, printer); | |
} | |
/** | |
* Do any required validation for this node. Throw an exception if it's incorrect. | |
*/ | |
public void validateNode() { | |
if (this.firstChild.isTableExpression()) { | |
throw QueryException.cannotCompareTablesInExpression(((TableExpression)this.firstChild).getTable()); | |
} | |
if (this.secondChild.isTableExpression()) { | |
throw QueryException.cannotCompareTablesInExpression(((TableExpression)this.secondChild).getTable()); | |
} | |
} | |
} |