| /* |
| * Copyright (c) 1998, 2019 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| // 05/24/2011-2.3 Guy Pelletier |
| // - 345962: Join fetch query when using tenant discriminator column fails. |
| package org.eclipse.persistence.internal.expressions; |
| |
| import java.io.BufferedWriter; |
| import java.io.IOException; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| |
| 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.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.queries.ContainerPolicy; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.queries.DatabaseQuery; |
| |
| /** |
| * Used for parameterized expressions, such as expression defined in mapping queries. |
| */ |
| public class ParameterExpression extends BaseExpression { |
| |
| /** The parameter field or name. */ |
| protected DatabaseField field; |
| |
| /** The opposite side of the relation, this is used for conversion of the parameter using the others mapping. */ |
| protected Expression localBase; |
| |
| protected boolean isProperty = false; |
| |
| /** The inferred type of the parameter. |
| * Please note that the type might not be always initialized to correct value. |
| * It might be null if not initialized correctly. |
| */ |
| Object type; |
| |
| public ParameterExpression() { |
| super(); |
| } |
| |
| public ParameterExpression(String fieldName) { |
| this(new DatabaseField(fieldName)); |
| } |
| |
| public ParameterExpression(DatabaseField field) { |
| super(); |
| this.field = field; |
| } |
| |
| // For bug 3107049 ParameterExpression will now be built with a |
| // default localBase, same as with ConstantExpression. |
| public ParameterExpression(String fieldName, Expression localbaseExpression, Object type) { |
| this(new DatabaseField(fieldName), localbaseExpression); |
| this.type = type; |
| } |
| |
| public ParameterExpression(DatabaseField field, Expression localbaseExpression) { |
| super(); |
| this.field = field; |
| localBase = localbaseExpression; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the expression is equal to the other. |
| * This is used to allow dynamic expression's SQL to be cached. |
| */ |
| @Override |
| public boolean equals(Object object) { |
| if (this == object) { |
| return true; |
| } |
| if (!super.equals(object)) { |
| return false; |
| } |
| ParameterExpression expression = (ParameterExpression) object; |
| return ((getField() == expression.getField()) || ((getField() != null) && getField().equals(expression.getField()))); |
| } |
| |
| /** |
| * 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 (getField() != null) { |
| hashCode = hashCode + getField().hashCode(); |
| } |
| return hashCode; |
| } |
| |
| /** |
| * Return description. |
| * Used for toString. |
| */ |
| public String basicDescription() { |
| return String.valueOf(getField()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for debug printing. |
| */ |
| @Override |
| public String descriptionOfNodeType() { |
| return "Parameter"; |
| } |
| |
| /** |
| * This allows for nesting of parameterized expression. |
| * This is used for parameterizing object comparisons. |
| */ |
| @Override |
| public Expression get(String attributeOrQueryKey) { |
| ParameterExpression expression = new ParameterExpression(attributeOrQueryKey); |
| expression.setBaseExpression(this); |
| |
| return expression; |
| } |
| |
| /** |
| * Return the expression builder which is the ultimate base of this expression, or |
| * null if there isn't one (shouldn't happen if we start from a root) |
| */ |
| @Override |
| public ExpressionBuilder getBuilder() { |
| if (localBase == null) { |
| //Bug#5097278 Need to return the builder from the base expression if nested. |
| if (getBaseExpression() != null) { |
| return getBaseExpression().getBuilder(); |
| } else { |
| return null; |
| } |
| } |
| return localBase.getBuilder(); |
| } |
| |
| public DatabaseField getField() { |
| return field; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to set the internal field value. |
| */ |
| public void setField(DatabaseField field) { |
| this.field = field; |
| } |
| |
| /** |
| * This allows for nesting of parametrized expression. |
| * This is used for parameterizing object comparisons. |
| */ |
| @Override |
| public Expression getField(DatabaseField field) { |
| ParameterExpression expression = new ParameterExpression(field); |
| expression.setBaseExpression(this); |
| |
| return expression; |
| } |
| |
| /** |
| * The opposite side of the relation, this is used for conversion of the parameter using the others mapping. |
| */ |
| public Expression getLocalBase() { |
| return localBase; |
| } |
| |
| /** |
| * The inferred type of this parameter. |
| * Please note that the type might not be always initialized to correct value. |
| * It might be null if not initialized correctly |
| */ |
| public Object getType() { return type; } |
| |
| /** |
| * The inferred type of this parameter. |
| * Please note that the type might not be always initialized to correct value. |
| * It might be null if not initialized correctly |
| */ |
| public void setType(Object type) { |
| this.type = type; |
| } |
| |
| /** |
| * Extract the value from the row. |
| * This may require recursion if it is a nested parameter. |
| */ |
| public Object getValue(AbstractRecord translationRow, AbstractSession session) { |
| return getValue(translationRow, null, session); |
| } |
| |
| /** |
| * Extract the value from the row. |
| * This may require recursion if it is a nested parameter. |
| */ |
| public Object getValue(AbstractRecord translationRow, DatabaseQuery query, AbstractSession session) { |
| if (this.field == null) { |
| return null; |
| } |
| |
| Object value = null; |
| |
| // Check for nested parameters. |
| if (this.baseExpression != null) { |
| value = ((ParameterExpression)this.baseExpression).getValue(translationRow, query, session); |
| if (value == null) { |
| return null; |
| } |
| |
| ClassDescriptor descriptor = session.getDescriptor(value); |
| //Bug4924639 Aggregate descriptors have to be acquired from their mapping as they are cloned and initialized by each mapping |
| if (descriptor != null && descriptor.isAggregateDescriptor() && ((ParameterExpression)getBaseExpression()).getLocalBase().isObjectExpression()) { |
| descriptor = ((ObjectExpression)((ParameterExpression)getBaseExpression()).getLocalBase()).getDescriptor(); |
| } |
| |
| if (descriptor == null) { |
| // Bug 245268 validate parameter type against mapping |
| validateParameterValueAgainstMapping(value, true); |
| } else { |
| // For bug 2990493 must unwrap for EJBQL "Select Person(p) where p = ?1" |
| //if we had to unwrap it make sure we replace the argument with this value |
| //incase it is needed again, say in conforming. |
| //bug 3750793 |
| value = descriptor.getObjectBuilder().unwrapObject(value, session); |
| |
| // Bug 245268 must unwrap before validating parameter type |
| validateParameterValueAgainstMapping(value, true); |
| |
| translationRow.put(((ParameterExpression)this.baseExpression).getField(), value); |
| |
| // The local parameter is either a field or attribute of the object. |
| DatabaseMapping mapping = descriptor.getObjectBuilder().getMappingForField(this.field); |
| if (mapping != null) { |
| value = mapping.valueFromObject(value, this.field, session); |
| } else { |
| mapping = descriptor.getObjectBuilder().getMappingForAttributeName(this.field.getName()); |
| if (mapping != null) { |
| value = mapping.getRealAttributeValueFromObject(value, session); |
| } else { |
| DatabaseField queryKeyField = descriptor.getObjectBuilder().getFieldForQueryKeyName(this.field.getName()); |
| if (queryKeyField != null) { |
| mapping = descriptor.getObjectBuilder().getMappingForField(this.field); |
| if (mapping != null) { |
| value = mapping.valueFromObject(value, this.field, session); |
| } |
| } |
| } |
| } |
| } |
| } else { |
| // Check for null translation row. |
| if (translationRow == null) { |
| value = AbstractRecord.noEntry; |
| } else { |
| value = translationRow.getIndicatingNoEntry(this.field); |
| } |
| |
| // Throw an exception if the field is not mapped. Null may be |
| // returned if it is a property so check for null and isProperty |
| if ((value == AbstractRecord.noEntry) || ((value == null) && this.isProperty)) { |
| if (this.isProperty) { |
| if (query != null) { |
| value = query.getSession().getProperty(this.field.getName()); |
| } else { |
| value = session.getProperty(this.field.getName()); |
| } |
| |
| if (value == null) { |
| throw QueryException.missingContextPropertyForPropertyParameterExpression(query, this.field.getName()); |
| } |
| |
| return value; |
| } |
| // Also check the same field, but a different table for table per class inheritance. |
| // TODO: JPA also allows for field to be renamed in subclasses, this needs to account for that (never has...). |
| if (translationRow != null) { |
| value = translationRow.getIndicatingNoEntry(new DatabaseField(this.field.getName())); |
| } |
| if ((value == AbstractRecord.noEntry) || (value == null)) { |
| throw QueryException.parameterNameMismatch(this.field.getName()); |
| } |
| } |
| |
| // validate parameter type against mapping |
| // validate against the localbase (false), since there are no nested params |
| validateParameterValueAgainstMapping(value, false); |
| } |
| |
| // Convert the value to the correct type, i.e. object type mappings. |
| if (this.localBase != null) { |
| value = this.localBase.getFieldValue(value, session); |
| } |
| |
| return value; |
| } |
| |
| @Override |
| public boolean isParameterExpression() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isValueExpression() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this parameter expression maps to a property. |
| */ |
| public boolean isProperty() { |
| return isProperty; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for cloning. |
| */ |
| @Override |
| protected void postCopyIn(Map alreadyDone) { |
| super.postCopyIn(alreadyDone); |
| if (getLocalBase() != null) { |
| setLocalBase(getLocalBase().copiedVersionFrom(alreadyDone)); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Print SQL onto the stream, using the ExpressionPrinter for context |
| */ |
| @Override |
| public void printSQL(ExpressionSQLPrinter printer) { |
| if (printer.shouldPrintParameterValues()) { |
| Object value = getValue(printer.getTranslationRow(), printer.getSession()); |
| if (value instanceof Collection) { |
| printer.printValuelist((Collection)value); |
| }else{ |
| if(getField() == null) { |
| printer.printPrimitive(value); |
| } else { |
| printer.printParameter(this); |
| } |
| } |
| } else { |
| if (getField() != null) { |
| printer.printParameter(this); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Print java for project class generation |
| */ |
| @Override |
| public void printJava(ExpressionJavaPrinter printer) { |
| ((DataExpression)getLocalBase()).getBaseExpression().printJava(printer); |
| printer.printString(".getParameter(\"" + getField().getQualifiedName() + "\")"); |
| } |
| |
| /** |
| * 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) { |
| ParameterExpression result = (ParameterExpression)clone(); |
| result.setLocalBase(localBase.rebuildOn(newBase)); |
| return result; |
| } |
| |
| /** |
| * 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){ |
| return; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set to true if this parameter expression maps to a property value. |
| */ |
| public void setIsProperty(boolean isProperty) { |
| this.isProperty = isProperty; |
| } |
| |
| /** |
| * The opposite side of the relation, this is used for conversion of the parameter using the others mapping. |
| */ |
| @Override |
| public void setLocalBase(Expression localBase) { |
| this.localBase = localBase; |
| } |
| |
| /** |
| * INTERNAL: |
| * Rebuild 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 (isProperty()) { |
| return context.getProperty(getField()); |
| } else if (newBase == oldBase) { |
| return this; |
| } else { |
| return context.getField(getField()); |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Validate the passed parameter against the local base mapping. |
| * Throw a QueryException if the parameter is of an incorrect class for object comparison. |
| * Added for Bug 245268 |
| */ |
| protected void validateParameterValueAgainstMapping(Object value, boolean useBaseExpression) { |
| Expression queryKey = null; |
| if (useBaseExpression) { |
| // used to support validating against the base expression in the case of nesting |
| ParameterExpression baseExpression = (ParameterExpression)getBaseExpression(); |
| queryKey = baseExpression.getLocalBase(); |
| } else { |
| // used where we need to simply validate against the local base expression |
| queryKey = this.getLocalBase(); |
| } |
| |
| if ((value != null) && !(value instanceof Collection) && (queryKey != null) && queryKey.isObjectExpression()) { |
| DatabaseMapping mapping = ((ObjectExpression) queryKey).getMapping(); |
| if (mapping != null) { |
| if (mapping.isCollectionMapping() && queryKey.isMapEntryExpression() && !((MapEntryExpression)queryKey).shouldReturnMapEntry()){ |
| // this is a map key expression, operate on the key |
| ContainerPolicy cp = mapping.getContainerPolicy(); |
| Object keyType = cp.getKeyType(); |
| Class keyTypeClass = keyType instanceof Class ? (Class)keyType: ((ClassDescriptor)keyType).getJavaClass(); |
| if (!keyTypeClass.isInstance(value)){ |
| throw QueryException.incorrectClassForObjectComparison(baseExpression, value, mapping); |
| } |
| } else if (mapping.isDirectCollectionMapping()) { |
| // Do not validate direct collection, as type may be convertable. |
| } else if (mapping.isForeignReferenceMapping() && !mapping.getReferenceDescriptor().getJavaClass().isInstance(value)) { |
| throw QueryException.incorrectClassForObjectComparison(baseExpression, value, mapping); |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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) { |
| // Run ourselves through the translation row to find the desired value |
| if (getField() != null) { |
| return getValue(translationRow, session); |
| } |
| |
| throw QueryException.cannotConformExpression(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to print a debug form of the expression tree. |
| */ |
| @Override |
| public void writeDescriptionOn(BufferedWriter writer) throws IOException { |
| writer.write(basicDescription()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Append the parameter into the printer. |
| * "Normal" ReadQuery never has ParameterExpression in it's select clause hence for a "normal" ReadQuery this method is never called. |
| * The reason this method was added is that UpdateAllQuery (in case temporary storage is required) |
| * creates a "helper" ReportQuery with ReportItem corresponding to each update expression - and update expression |
| * may be a ParameterExpression. The call created by "helper" ReportQuery is never executed - |
| * it's used during construction of insert call into temporary storage. |
| */ |
| @Override |
| public void writeFields(ExpressionSQLPrinter printer, List<DatabaseField> newFields, SQLSelectStatement statement) { |
| if (printer.getPlatform().isDynamicSQLRequiredForFunctions()) { |
| printer.getCall().setUsesBinding(false); |
| } |
| //print ", " before each selected field except the first one |
| if (printer.isFirstElementPrinted()) { |
| printer.printString(", "); |
| } else { |
| printer.setIsFirstElementPrinted(true); |
| } |
| |
| // This field is a parameter value, so any name can be used. |
| newFields.add(new DatabaseField("*")); |
| printSQL(printer); |
| } |
| |
| /** |
| * Print the base for debuggin purposes. |
| */ |
| @Override |
| public void writeSubexpressionsTo(BufferedWriter writer, int indent) throws IOException { |
| if (getBaseExpression() != null) { |
| getBaseExpression().toString(writer, indent); |
| } |
| } |
| } |