| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2019 IBM Corporation. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| // IBM - Bug 537795: CASE THEN and ELSE scalar expression Constants should not be casted to CASE operand type |
| 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<Expression> 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; |
| Expression child = this.children.get(index); |
| if (child instanceof Expression) { |
| valueFromRight = 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<Expression> getChildren() { |
| return this.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 List<DatabaseField> 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<Expression> childrenEnum = this.children.elements(); childrenEnum.hasMoreElements();) { |
| Expression child = 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, 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++) { |
| 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(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 = 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, 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<Expression> oldChildren = this.children; |
| this.children = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(); |
| for (int i = 0; i < oldChildren.size(); i++) { |
| addChild((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<Expression> iterator = this.children.iterator(); iterator.hasNext(); ) { |
| Expression child = 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(this.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 |
| this.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(this.children.elementAt(index).twistedForBaseAndContext(newBase, context, oldBase)); |
| } |
| |
| // Aply the function to the twisted old base. |
| Expression oldBaseExp = 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(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, List<DatabaseField> 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.add(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.add(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() && (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<DatabaseMapping> i = descriptor.getMappings().iterator(); i.hasNext(); ) { |
| DatabaseMapping m = i.next(); |
| if (m.isPrimaryKeyMapping()) { |
| return m; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Lookup the mapping for this item by traversing its expression recursively. |
| */ |
| @Override |
| public DatabaseMapping getLeafMapping(DatabaseQuery query, ClassDescriptor rootDescriptor, AbstractSession session) { |
| int selector = this.operator.getSelector(); |
| |
| //MAX and MIN functions require mappings for their result value. See JPA 2.1; section 4.8.5 |
| if (this.baseExpression != null && ((selector == ExpressionOperator.Maximum) || (selector == ExpressionOperator.Minimum))) { |
| return this.baseExpression.getLeafMapping(query, rootDescriptor, session); |
| } |
| return super.getLeafMapping(query, rootDescriptor, session); |
| } |
| } |