/******************************************************************************* * 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 ******************************************************************************/ package org.eclipse.persistence.internal.expressions; import java.io.BufferedWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.exceptions.QueryException; import org.eclipse.persistence.expressions.Expression; import org.eclipse.persistence.expressions.ExpressionBuilder; import org.eclipse.persistence.expressions.ExpressionOperator; import org.eclipse.persistence.history.AsOfClause; import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform; import org.eclipse.persistence.internal.helper.ClassConstants; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.helper.DatabaseTable; import org.eclipse.persistence.internal.helper.NonSynchronizedVector; import org.eclipse.persistence.internal.queries.ReportItem; import org.eclipse.persistence.internal.sessions.AbstractRecord; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.mappings.DatabaseMapping; import org.eclipse.persistence.mappings.OneToOneMapping; 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.ReadQuery; import org.eclipse.persistence.queries.ReportQuery; /** * Used for expressions that have 0 to n children. * These include not, between and all functions. */ public class FunctionExpression extends BaseExpression { protected Vector children; protected ExpressionOperator operator; protected transient ExpressionOperator platformOperator; protected Class resultType; public FunctionExpression() { this.children = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(2); this.resultType = null; } /** * INTERNAL: * Return if the expression is equal to the other. * This is used to allow dynamic expression's SQL to be cached. * This must be over written by each subclass. */ @Override public boolean equals(Object object) { if (this == object) { return true; } if (!super.equals(object)) { return false; } FunctionExpression expression = (FunctionExpression) object; if ((this.operator != expression.getOperator()) && ((this.operator == null) || (!this.operator.equals(expression.getOperator())))) { return false; } List children = getChildren(); List otherChildren = expression.getChildren(); int size = children.size(); if (size != otherChildren.size()) { return false; } for (int index = 0; index < size; index++) { if (!children.get(index).equals(otherChildren.get(index))) { return false; } } return true; } /** * 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 (this.operator != null) { hashCode = hashCode + this.operator.hashCode(); } List children = getChildren(); int size = children.size(); for (int index = 0; index < size; index++) { hashCode = hashCode + children.get(index).hashCode(); } return hashCode; } public void addChild(Expression child) { getChildren().addElement(child); } /** * INTERNAL: * Find the alias for a given table */ @Override public DatabaseTable aliasForTable(DatabaseTable table) { return getBaseExpression().aliasForTable(table); } @Override public Expression asOf(AsOfClause clause) { final AsOfClause finalClause = clause; ExpressionIterator iterator = new ExpressionIterator() { @Override public void iterate(Expression each) { if (each.isDataExpression()) { each.asOf(finalClause); } } @Override public boolean shouldIterateOverSubSelects() { return true; } }; iterator.iterateOn(this); return this; } /** * INTERNAL: */ @Override public Expression create(Expression base, Object singleArgument, ExpressionOperator anOperator) { baseExpression = base; addChild(base); Expression localBase = base; if(anOperator.isFunctionOperator()) { ExpressionBuilder builder = getBuilder(); if(builder != null) { localBase = builder; } } Expression arg = Expression.from(singleArgument, localBase); addChild(arg); setOperator(anOperator); return this; } /** * INTERNAL: * added for Trim support. TRIM([trim_character FROM] string_primary) */ @Override public Expression createWithBaseLast(Expression base, Object singleArgument, ExpressionOperator anOperator) { baseExpression = base; Expression localBase = base; if(anOperator.isFunctionOperator()) { ExpressionBuilder builder = getBuilder(); if(builder != null) { localBase = builder; } } Expression arg = Expression.from(singleArgument, localBase); addChild(arg); addChild(base); setOperator(anOperator); return this; } /** * INTERNAL: */ @Override public Expression create(Expression base, List arguments, ExpressionOperator anOperator) { this.baseExpression = base; setOperator(anOperator); addChild(base); Expression localBase = base; if (anOperator.isFunctionOperator()) { ExpressionBuilder builder = getBuilder(); if (builder != null) { localBase = builder; } } for (Object argument : arguments) { Expression arg = Expression.from(argument, localBase); addChild(arg); } return this; } /** * INTERNAL: * Used for debug printing. */ @Override public String descriptionOfNodeType() { return "Function"; } /** * 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. */ @Override public boolean doesConform(Object object, AbstractSession session, AbstractRecord translationRow, int valueHolderPolicy, boolean isObjectUnregistered) { int selector = this.operator.getSelector(); // Must check for NOT and negate entire base expression. if (selector == ExpressionOperator.Not) { return !getBaseExpression().doesConform(object, session, translationRow, valueHolderPolicy, isObjectUnregistered); } // Conform between or in function. if ((selector == ExpressionOperator.Between) || (selector == ExpressionOperator.NotBetween) || (selector == ExpressionOperator.In) || (selector == ExpressionOperator.NotIn) || (selector == ExpressionOperator.Like) || (selector == ExpressionOperator.Regexp) || (selector == ExpressionOperator.NotLike)) { // Extract the value from the left side. Object leftValue = getBaseExpression().valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered); // Extract the value from the arguments, skip the first child which is the base. int size = this.children.size(); Vector rightValue = new Vector(size); for (int index = 1; index < size; index++) { Object valueFromRight; Object child = this.children.get(index); if (child instanceof Expression) { valueFromRight = ((Expression)child).valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered); } else { valueFromRight = child; } //If valueFromRight is a Vector, then there is only one child other than the base, e.g. valueFromRight is a collection of constants. //Then it should be the vector to be compared with. Don't add it to another collection. if (valueFromRight instanceof Vector) { rightValue = (Vector)valueFromRight; //Single values should be added to the rightValue, which will be compared with leftValue. } else { rightValue.add(valueFromRight); } } // If left is anyof collection of values, check each one. // If the right had an anyof not supported will be thrown from the operator. if (leftValue instanceof Vector) { for (Object tempLeft : (Vector)leftValue) { if (this.operator.doesRelationConform(tempLeft, rightValue)) { return true; } } // Only return false if none of the values match. return false; } else { return this.operator.doesRelationConform(leftValue, rightValue); } } else if ((selector == ExpressionOperator.IsNull) || (selector == ExpressionOperator.NotNull)) { // Extract the value from the left side. Object leftValue = getBaseExpression().valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered); // If left is anyof collection of values, check each one. if (leftValue instanceof Vector) { for (Object tempLeft : (Vector)leftValue) { if (this.operator.doesRelationConform(tempLeft, null)) { return true; } } // Only return false if none of the values match. return false; } else { return this.operator.doesRelationConform(leftValue, null); } } // No other relation functions are supported. // Non-relation functions are supported through valueFromObject(). throw QueryException.cannotConformExpression(); } public Vector getChildren() { return children; } /** * INTERNAL: Not to be confused with the public getField(String) * This returns a collection of all fields associated with this object. Really * only applies to query keys representing an object or to expression builders. * */ @Override public Vector getFields() { return getBaseExpression().getFields(); } /** * INTERNAL: */ @Override public List<DatabaseField> getSelectionFields(ReadQuery query) { return getBaseExpression().getSelectionFields(query); } @Override public ExpressionOperator getOperator() { return operator; } public ExpressionOperator getPlatformOperator(DatabasePlatform platform) { if (platformOperator == null) { initializePlatformOperator(platform); } return platformOperator; } public Class getResultType() { return resultType; } public boolean hasResultType() { return resultType != null; } /** * INTERNAL: */ public void initializePlatformOperator(DatabasePlatform platform) { if (this.operator.isComplete()) { platformOperator = this.operator; return; } platformOperator = platform.getOperator(this.operator.getSelector()); if (platformOperator == null) { throw QueryException.invalidOperator(this.operator.toString()); } } @Override public boolean isFunctionExpression() { return true; } /** * INTERNAL: * Return if the represents an object comparison. */ protected boolean isObjectComparison() { int selector = this.operator.getSelector(); if (((selector != ExpressionOperator.IsNull) && (selector != ExpressionOperator.NotNull)) || (this.children.size() != 1)) { if (((selector != ExpressionOperator.InSubQuery) && (selector != ExpressionOperator.NotInSubQuery)) || (this.children.size() != 2)) { return false; } } Expression base = getBaseExpression(); //bug 384641 - check that directCollections are not treated as object comparisons return (base.isObjectExpression() && (!((ObjectExpression)base).isAttribute()) && !((ObjectExpression)base).isDirectCollection() ); } /** * INTERNAL: * For iterating using an inner class */ @Override public void iterateOn(ExpressionIterator iterator) { super.iterateOn(iterator); for (Enumeration childrenEnum = this.children.elements(); childrenEnum.hasMoreElements();) { Expression child = (Expression)childrenEnum.nextElement(); child.iterateOn(iterator); } } /** * INTERNAL: * Normalize into a structure that is printable. * Also compute printing information such as outer joins. * This checks for object isNull, notNull, in and notIn comparisons. */ @Override public Expression normalize(ExpressionNormalizer normalizer) { //This method has no validation but we should still make the method call for consistency //bug # 2956674 //validation is moved into normalize to ensure that expressions are valid before we attempt to work with them validateNode(); if (this.children.isEmpty()) { return this; } // Ensure session has been set. ExpressionBuilder builder = getBuilder(); if ((builder != null) && (builder.getSession() == null)) { builder.setSession(normalizer.getSession().getRootSession(null)); } if (this.operator.getSelector() == ExpressionOperator.Count) { // Attempting to count an Entity and not an attribute. Need to augment this expression. // This is normally normalized in ReportQuery, but can get to here in a having clause. prepareObjectAttributeCount(normalizer, null, null, null); } if (!isObjectComparison()) { for (int index = 0; index < this.children.size(); index++) { this.children.set(index, ((Expression)this.children.get(index)).normalize(normalizer)); } return this; } else { //if not normalizing we must still validate the corresponding node to make sure that they are valid //bug # 2956674 for (int index = 0; index < this.children.size(); index++) { ((Expression)this.children.get(index)).validateNode(); } } // This code is executed only in the case of an is[not]Null, or [not]in on an // object attribute. ObjectExpression base = (ObjectExpression)getBaseExpression(); // For cr2334, fix code so that normalize is first called on base expressions. // I.e. if base itself had a base expression this expression would not be normalized. if (base.getBaseExpression() != null) { base.getBaseExpression().normalize(normalizer); } // Check for IN with objects, "e IN (Select e2 from Employee e2)". if ((this.operator.getSelector() == ExpressionOperator.InSubQuery) || (this.operator.getSelector() == ExpressionOperator.NotInSubQuery)) { // Switch object comparison to compare on primary key. if (this.children.size() != 2) { 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 (base.isQueryKeyExpression()) { mapping = base.getMapping(); } List<DatabaseField> sourceFields = null; List<DatabaseField> targetFields = null; if ((mapping != null) && mapping.isOneToOneMapping() && (!((OneToOneMapping)mapping).hasRelationTableMechanism()) && (!((OneToOneMapping)mapping).hasCustomSelectionQuery())) { base = (ObjectExpression)base.getBaseExpression(); 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; sourceFields = base.getDescriptor().getPrimaryKeyFields(); targetFields = sourceFields; } if (sourceFields.size() != 1) { base = (ObjectExpression)getBaseExpression(); // For composite ids an exists and subselect is used. SubSelectExpression subSelectExp = (SubSelectExpression)this.children.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(); Expression newExp; // Any or Some if (this.operator.getSelector() == ExpressionOperator.InSubQuery) { subSelectCriteria = subBuilder.equal(base).and(subSelectCriteria); } else { subSelectCriteria = subBuilder.notEqual(base).and(subSelectCriteria); } subQuery.setSelectionCriteria(subSelectCriteria); newExp = builder.exists(subQuery); return newExp.normalize(normalizer); } Expression newBase = base.getField(sourceFields.get(0)); setBaseExpression(newBase); this.children.set(0, newBase); Expression right = (Expression)this.children.get(1); if (right.isSubSelectExpression()) { // Check for sub-select, need to replace sub-selects on object to select its id. ReportQuery query = ((SubSelectExpression)right).getSubQuery(); if (query.getItems().size() != 1) { throw QueryException.invalidExpression(this); } ReportItem item = query.getItems().get(0); item.setAttributeExpression(item.getAttributeExpression().getField(targetFields.get(0))); } else { throw QueryException.invalidExpression(this); } // Still need to normalize the children. for (int index = 0; index < this.children.size(); index++) { this.children.set(index, ((Expression)this.children.get(index)).normalize(normalizer)); } return this; } // else isNull/notNull Expression foreignKeyJoin = null; if (base.getMapping() == null) { // Is an expression builder, transform to a null primary key expression. foreignKeyJoin = base.getDescriptor().getObjectBuilder().buildPrimaryKeyExpressionFromKeys(null, getSession()); foreignKeyJoin = foreignKeyJoin.rebuildOn(base); } else { // Switch to null foreign key comparison (i.e. get('c').isNull() to getField('C_ID').isNull()). // For bug 3105559 also must handle aggregates: get("period").isNull(); foreignKeyJoin = base.getMapping().buildObjectJoinExpression(base, (Object)null, getSession()); } if (this.operator.getSelector() == ExpressionOperator.NotNull) { foreignKeyJoin = foreignKeyJoin.not(); } return foreignKeyJoin.normalize(normalizer); } /** * INTERNAL: * Used for cloning. */ @Override protected void postCopyIn(Map alreadyDone) { super.postCopyIn(alreadyDone); Vector oldChildren = children; children = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(); for (int i = 0; i < oldChildren.size(); i++) { addChild((((Expression)oldChildren.elementAt(i)).copiedVersionFrom(alreadyDone))); } } /** * INTERNAL: * Print SQL using the operator. */ @Override public void printSQL(ExpressionSQLPrinter printer) { // If all children are parameters, some databases don't allow binding. if (printer.getPlatform().isDynamicSQLRequiredForFunctions() && !this.children.isEmpty()) { boolean allParams = true; for (Iterator iterator = this.children.iterator(); iterator.hasNext(); ) { Expression child = (Expression)iterator.next(); if (!(child.isParameterExpression() || child.isConstantExpression())) { allParams = false; } } if (allParams) { printer.getCall().setUsesBinding(false); } } ExpressionOperator realOperator; realOperator = getPlatformOperator(printer.getPlatform()); realOperator.printCollection(this.children, printer); } /** * INTERNAL: * Print java for project class generation */ @Override public void printJava(ExpressionJavaPrinter printer) { ExpressionOperator realOperator = getPlatformOperator(printer.getPlatform()); realOperator.printJavaCollection(this.children, printer); } /** * 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 = getBaseExpression().rebuildOn(newBase); Vector newChildren = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(this.children.size()); for (int i = 1; i < this.children.size(); i++) {// Skip the first one, since it's also the base newChildren.addElement(((Expression)children.elementAt(i)).rebuildOn(newBase)); } newLocalBase.setSelectIfOrderedBy(getBaseExpression().selectIfOrderedBy()); FunctionExpression rebuilt = (FunctionExpression) newLocalBase.performOperator(this.operator, newChildren); rebuilt.setResultType(this.getResultType()); //copy over result type. return rebuilt; } /** * INTERNAL: * Search the tree for any expressions (like SubSelectExpressions) that have been * built using a builder that is not attached to the query. This happens in case of an Exists * call using a new ExpressionBuilder(). This builder needs to be replaced with one from the query. */ @Override public void resetPlaceHolderBuilder(ExpressionBuilder queryBuilder){ getBaseExpression().resetPlaceHolderBuilder(queryBuilder); for (int i = this.children.size()-1; i > 0; i--) {// Skip the first one, since it's also the base ((Expression)children.elementAt(i)).resetPlaceHolderBuilder(queryBuilder); } } // Set the local base expression, ie the one on the other side of the operator // Most types will ignore this, since they don't need it. @Override public void setLocalBase(Expression exp) { getBaseExpression().setLocalBase(exp); } public void setOperator(ExpressionOperator theOperator) { operator = theOperator; } public void setResultType(Class resultType) { this.resultType = resultType; } /** * 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 (this.children.isEmpty()) { return (Expression)clone(); } Vector newChildren = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(this.children.size()); // For functions the base is the first child, we only want the arguments so start at the second. for (int index = 1; index < this.children.size(); index++) { newChildren.addElement(((Expression)children.elementAt(index)).twistedForBaseAndContext(newBase, context, oldBase)); } // Aply the function to the twisted old base. Expression oldBaseExp = (Expression)this.children.elementAt(0); return oldBaseExp.twistedForBaseAndContext(newBase, context, oldBase).performOperator(this.operator, newChildren); } /** * 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) { Object baseValue = getBaseExpression().valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered); Vector arguments = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(this.children.size()); for (int index = 1; index < this.children.size(); index++) { if (this.children.elementAt(index) instanceof Expression) { arguments.addElement(((Expression)this.children.elementAt(index)).valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered)); } else { arguments.addElement(this.children.elementAt(index)); } } if (baseValue instanceof Vector) {// baseValue might be a vector, so the individual values must be extracted before applying the function call to them Vector baseVector = new Vector(); for (Enumeration valuesToCompare = ((Vector)baseValue).elements(); valuesToCompare.hasMoreElements();) { Object baseObject = valuesToCompare.nextElement(); if (baseObject == null) { baseVector.addElement(null); } else { baseVector.addElement(this.operator.applyFunction(baseObject, arguments)); } } return baseVector; } else { // Do not apply functions to null, just leave as null. if (baseValue == null) { return null; } else { return this.operator.applyFunction(baseValue, arguments); } } } /** * INTERNAL: * Used to print a debug form of the expression tree. */ @Override public void writeDescriptionOn(BufferedWriter writer) throws IOException { writer.write(operator.toString()); } /** * INTERNAL: called from SQLSelectStatement.writeFieldsFromExpression(...) */ @Override public void writeFields(ExpressionSQLPrinter printer, Vector newFields, SQLSelectStatement statement) { //print ", " before each selected field except the first one if (printer.isFirstElementPrinted()) { printer.printString(", "); } else { printer.setIsFirstElementPrinted(true); } if (getBaseExpression().isDataExpression()) { DatabaseField field = ((DataExpression)getBaseExpression()).getField(); if (field == null) { // This means the select wants a *. field = new DatabaseField("*"); } else { // Clone the field since we will change its type. field = field.clone(); } // If the result type is set, use it. field.setSqlType(DatabaseField.NULL_SQL_TYPE); //we also cache the JDBC type now so reset it as well. if (hasResultType()) { field.setType(getResultType()); } else { // If the function is anything but min or max, null out the // field type. The type will be calculated based on the // function. int selector = this.operator.getSelector(); if (selector != ExpressionOperator.Maximum && selector != ExpressionOperator.Minimum) { field.setType(null); } } newFields.addElement(field); } else { // This field is a complex function value so any name can be used. DatabaseField field = new DatabaseField("*"); // If the result type is set, use it. field.setSqlType(DatabaseField.NULL_SQL_TYPE); field.setType(getResultType()); newFields.addElement(field); } printSQL(printer); } /** * INTERNAL: * Used in SQL printing. */ @Override public void writeSubexpressionsTo(BufferedWriter writer, int indent) throws IOException { if (baseExpression != null) { baseExpression.toString(writer, indent); } } /** * INTERNAL: * JPQL allows count([distinct] e), where e can be an object, not just a single field, * however the database only allows a single field, so object needs to be translated to a single field. * If the descriptor has a single pk, it is used, otherwise any pk is used if distinct, otherwise a subselect is used. * If the object was obtained through an outer join, then the subselect also will not work, so an error is thrown. */ public void prepareObjectAttributeCount(ExpressionNormalizer normalizer, ReportItem item, ReportQuery query, Map clonedExpressions) { // ** Note that any of the arguments may be null depending on the caller. if (getOperator().getSelector() == ExpressionOperator.Count) { Expression baseExp = getBaseExpression(); boolean distinctUsed = false; if (baseExp.isFunctionExpression() && (((FunctionExpression)baseExp).getOperator().getSelector() == ExpressionOperator.Distinct)) { distinctUsed = true; baseExp = ((FunctionExpression)baseExp).getBaseExpression(); } boolean outerJoin = false; ClassDescriptor newDescriptor = null; AbstractSession session = null; if (query != null) { session = query.getSession(); } else { session = normalizer.getSession(); } if (baseExp.isQueryKeyExpression()) { // now need to find out if it is a direct to field or something else. ClassDescriptor descriptor = null; if (query == null) { descriptor = ((QueryKeyExpression) baseExp).getDescriptor(); } else { descriptor = query.getDescriptor(); } DatabaseMapping mapping = baseExp.getLeafMapping(query, descriptor, session); if ((mapping != null) && !mapping.isAbstractDirectMapping()) { outerJoin = ((QueryKeyExpression)baseExp).shouldUseOuterJoin(); if (mapping.isAggregateMapping()){ newDescriptor = mapping.getDescriptor(); baseExp = ((QueryKeyExpression)baseExp).getBaseExpression(); } else { newDescriptor = mapping.getReferenceDescriptor(); } } else { QueryKey queryKey = getLeafQueryKeyFor(query, baseExp, descriptor, session); if ((queryKey != null) && queryKey.isForeignReferenceQueryKey()){ outerJoin = ((QueryKeyExpression) baseExp).shouldUseOuterJoin(); newDescriptor = session.getDescriptor(((ForeignReferenceQueryKey)queryKey).getReferenceClass()); } } } else if (baseExp.isExpressionBuilder()) { if (((ExpressionBuilder)baseExp).getQueryClass() == null) { if (item != null) { item.setResultType(ClassConstants.INTEGER); } } else { newDescriptor = session.getDescriptor(((ExpressionBuilder)baseExp).getQueryClass()); } } if (newDescriptor != null) { // At this point we are committed to rewriting the query. if ((newDescriptor.getPrimaryKeyFields().size() == 1) || !distinctUsed) { // case 1: single PK => // treat COUNT(entity) as COUNT(entity.pk) Expression countArg = baseExp.getField(newDescriptor.getPrimaryKeyFields().get(0)); if (distinctUsed) { countArg = countArg.distinct(); } setBaseExpression(countArg); getChildren().set(0, countArg); } else if (((DatabasePlatform)session.getPlatform(newDescriptor.getJavaClass())).supportsCountDistinctWithMultipleFields()) { // case 3, is database allows multiple fields, then just print them // treat COUNT(distinct entity) as COUNT(distinct entity.pk1, entity.pk2) List args = new ArrayList(newDescriptor.getPrimaryKeyFields().size()); Expression firstField = null; for (DatabaseField field : newDescriptor.getPrimaryKeyFields()) { if (firstField == null) { firstField = baseExp.getField(field); } else { args.add(baseExp.getField(field)); } } ExpressionOperator anOperator = new ExpressionOperator(); anOperator.setType(ExpressionOperator.FunctionOperator); Vector v = NonSynchronizedVector.newInstance(args.size()); v.addElement("DISTINCT "); for (int index = 0; index < args.size(); index++) { v.add(", "); } v.add(""); anOperator.printsAs(v); anOperator.bePrefix(); anOperator.setNodeClass(ClassConstants.FunctionExpression_Class); Expression distinctFunction = anOperator.expressionForArguments(firstField, args); setBaseExpression(distinctFunction); getChildren().set(0, distinctFunction); } else if (!outerJoin && (query != null)) { // case 4: composite PK and DISTINCT, but no // outer join => previous solution using // COUNT(*) and EXISTS subquery // TODO, this doesn't really work for most cases (joins, other things selected, group by), // this should probably be removed and throw an error, // or changed to just concat all the pks together. // If this is a subselect baseExp is yet uncloned, // and will miss out if moved now from items into a selection criteria. if (clonedExpressions != null) { if (clonedExpressions.get(baseExp.getBuilder()) != null) { baseExp = baseExp.copiedVersionFrom(clonedExpressions); } else { baseExp = baseExp.rebuildOn(query.getExpressionBuilder()); } } // Now the reference class of the query needs to be reversed. // See the bug description for an explanation. ExpressionBuilder countBuilder = baseExp.getBuilder(); ExpressionBuilder outerBuilder ; ReportQuery subSelect = new ReportQuery(query.getReferenceClass(), countBuilder); query.getSession().getPlatform().retrieveFirstPrimaryKeyOrOne(subSelect); // Make sure the outerBuilder does not appear on the left of the subselect. // Putting a builder on the left is desirable to trigger an optimization. if (query.getSelectionCriteria() != null) { outerBuilder = new ExpressionBuilder(newDescriptor.getJavaClass()); query.setExpressionBuilder(outerBuilder); subSelect.setSelectionCriteria(baseExp.equal(outerBuilder).and(query.getSelectionCriteria())); } else { outerBuilder = new ExpressionBuilder(newDescriptor.getJavaClass()); query.setExpressionBuilder(outerBuilder); subSelect.setSelectionCriteria(baseExp.equal(outerBuilder)); } query.setNonFetchJoinAttributeExpressions(null); query.setSelectionCriteria(outerBuilder.exists(subSelect)); setBaseExpression(outerBuilder); getChildren().set(0, outerBuilder); query.setReferenceClass(newDescriptor.getJavaClass()); query.changeDescriptor(session); } else { // case 4: composite PK, DISTINCT, outer join => // not supported, throw exception DatabaseQuery reportQuery = query; if (query == null) { reportQuery = normalizer.getStatement().getQuery(); } throw QueryException.distinctCountOnOuterJoinedCompositePK(newDescriptor, reportQuery); } } } } /** * INTERNAL: * Lookup the query key for this item. * If an aggregate of foreign mapping is found it is traversed. */ protected QueryKey getLeafQueryKeyFor(DatabaseQuery query, Expression expression, ClassDescriptor rootDescriptor, AbstractSession session) throws QueryException { // Check for database field expressions or place holder if ((expression == null) || (expression.isFieldExpression())) { return null; } if (!(expression.isQueryKeyExpression())) { return null; } QueryKeyExpression qkExpression = (QueryKeyExpression)expression; Expression baseExpression = qkExpression.getBaseExpression(); ClassDescriptor descriptor = baseExpression.getLeafDescriptor(query, rootDescriptor, session); return descriptor.getQueryKeyNamed(qkExpression.getName()); } protected DatabaseMapping getMappingOfFirstPrimaryKey(ClassDescriptor descriptor) { if (descriptor != null) { for (Iterator i = descriptor.getMappings().iterator(); i.hasNext(); ) { DatabaseMapping m = (DatabaseMapping)i.next(); if (m.isPrimaryKeyMapping()) { return m; } } } return null; } }