org.eclipse.persistence.internal.expressions.FieldExpression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
The newest version!
/*
* Copyright (c) 1998, 2024 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
package org.eclipse.persistence.internal.expressions;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Field expressions represent a field of a table.
* Their base is either a table or object expression.
* This is used for mapping queries and to allow queries on non-mapped field or tables.
*/
public class FieldExpression extends DataExpression {
protected DatabaseField field;
protected transient DatabaseField aliasedField;
/**
* FieldExpression constructor comment.
*/
public FieldExpression() {
super();
}
/**
* FieldExpression constructor comment.
*/
public FieldExpression(DatabaseField newField) {
super();
field = newField;
}
/**
* FieldExpression constructor comment.
*/
public FieldExpression(DatabaseField newField, Expression myBase) {
super();
field = newField;
baseExpression = myBase;
}
/**
* 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;
}
FieldExpression expression = (FieldExpression)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;
}
/**
* INTERNAL:
*/
@Override
public void clearAliases() {
super.clearAliases();
aliasedField = null;
}
/**
* INTERNAL:
* Used for debug printing.
*/
@Override
public String descriptionOfNodeType() {
return "Field";
}
/**
* INTERNAL:
* Return the field appropriately aliased
*/
@Override
public DatabaseField getAliasedField() {
if (aliasedField == null) {
initializeAliasedField();
}
return aliasedField;
}
/**
* Return the alias for our table
*/
private DatabaseTable getAliasedTable() {
DataExpression base = (DataExpression)getBaseExpression();
DatabaseField field = getField();
if (!field.hasTableName()) {
if (base.getDescriptor() != null){
field = base.getDescriptor().buildField(field);
}
}
DatabaseTable alias = base.aliasForTable(field.getTable());
if (alias == null) {
return field.getTable();
} else {
return alias;
}
}
/**
* INTERNAL:
* If there are any fields associated with this expression, return them
*/
@Override
public DatabaseField getClonedField() {
return getField().clone();
}
/**
* INTERNAL:
* If there are any fields associated with this expression, return them
*/
public List getClonedFields() {
List result = new ArrayList<>(1);
result.add(getField().clone());
return result;
}
/**
* INTERNAL:
*/
@Override
public DatabaseField getField() {
return field;
}
/**
* INTERNAL:
* Return all the fields
*/
@Override
public List getFields() {
List result = new ArrayList<>(1);
result.add(getField());
return result;
}
/**
* INTERNAL:
* Provide for conversion of the passed object's value, based on the referenced field value.
*/
@Override
public Object getFieldValue(Object value, AbstractSession session) {
// Bug 418705
if (getField() != null && value != null && (value.getClass() != getField().getType()) && !(value instanceof Collection) && !(value instanceof Enum)) {
try {
return session.getDatasourcePlatform().convertObject(value, getField().getType());
} catch (ConversionException c) {}
}
return super.getFieldValue(value, session);
}
/**
* INTERNAL:
* Alias the database field for our current environment
*/
private void initializeAliasedField() {
DatabaseField tempField = getField().clone();
DatabaseTable aliasedTable = getAliasedTable();
// Put in a special check here so that if the aliasing does nothing we don't cache the
// result because it's invalid. This saves us from caching premature data if e.g. debugging
// causes us to print too early"
// if (aliasedTable.equals(getField().getTable())) {
// return;
// } else {
aliasedField = tempField;
aliasedField.setTable(aliasedTable);
// }
}
/**
* INTERNAL:
*/
@Override
public boolean isAttribute() {
return true;
}
@Override
public boolean isFieldExpression() {
return true;
}
/**
* INTERNAL:
* Normalize the expression into a printable structure.
*/
@Override
public Expression normalize(ExpressionNormalizer normalizer) {
if (this.hasBeenNormalized) {
return this;
}
Expression expression = super.normalize(normalizer);
// to support custom types, print expressions derived from field expressions, table expressions and direct query keys with their aliases
if (getBaseExpression() != null && getBaseExpression().isFieldExpression() || getBaseExpression().isTableExpression() ||
(getBaseExpression().isQueryKeyExpression() && ((QueryKeyExpression)getBaseExpression()).isAttribute())){
getBuilder().getStatement().setRequiresAliases(true);
}
return expression;
}
/**
* INTERNAL:
* Print SQL onto the stream, using the ExpressionPrinter for context
*/
@Override
public void printSQL(ExpressionSQLPrinter printer) {
// to support custom types, print expressions derived from field expressions and direct query keys with their aliases
// Note: This is also necessary for TableExpressions, but they are taken care of by their associated FieldExpression.
if (getBaseExpression() != null && getBaseExpression().isFieldExpression() ||
(getBaseExpression().isQueryKeyExpression() && ((QueryKeyExpression)getBaseExpression()).isAttribute())){
getBaseExpression().printSQL(printer);
printer.printString(".");
}
printer.printField(getAliasedField());
}
/**
* INTERNAL:
* Print java for project class generation
*/
@Override
public void printJava(ExpressionJavaPrinter printer) {
getBaseExpression().printJava(printer);
printer.printString(".getField(\"" + 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) {
DatabaseField field = getField();
ClassDescriptor descriptor = null;
// Check for possible table per class rebuild and translate fields to correct table.
// TODO: JPA also allows for field to be renamed in subclasses, this needs to account for that (never has...).
if (getBaseExpression().isExpressionBuilder() && newBase.isObjectExpression()) {
if (newBase.getSession() != null) {
descriptor = ((ObjectExpression)newBase).getDescriptor();
}
if ((descriptor != null) && descriptor.hasTablePerClassPolicy()) {
field = field.clone();
field.setTable(descriptor.getDefaultTable());
}
}
FieldExpression expression = new FieldExpression(field, getBaseExpression().rebuildOn(newBase));
expression.setSelectIfOrderedBy(selectIfOrderedBy());
return expression;
}
/**
* INTERNAL:
* Set the field in the mapping.
*/
public void setField(DatabaseField newField) {
field = newField;
}
/**
* 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) {
Expression twistedBase = this.baseExpression.twistedForBaseAndContext(newBase, context, oldBase);
DatabaseField field = getField();
ClassDescriptor descriptor = null;
// Check for possible table per class rebuild and translate fields to correct table.
// TODO: JPA also allows for field to be renamed in subclasses, this needs to account for that (never has...).
if (this.baseExpression.isExpressionBuilder() && newBase.isObjectExpression()
&& ((this.baseExpression == oldBase) || (oldBase == null))) {
if (newBase.getSession() != null) {
descriptor = ((ObjectExpression)newBase).getDescriptor();
}
if ((descriptor != null) && descriptor.hasTablePerClassPolicy()) {
field = field.clone();
field.setTable(descriptor.getDefaultTable());
}
}
return twistedBase.getField(field);
}
/**
* Do any required validation for this node. Throw an exception if it's incorrect.
*/
@Override
public void validateNode() {
DataExpression base = (DataExpression)getBaseExpression();
if (getField().getTable().hasName()) {
List tables = base.getOwnedTables();
if ((tables != null) && (!tables.contains((getField().getTable())))) {
throw QueryException.invalidTableForFieldInExpression(getField());
}
}
}
/**
* 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) {
// Joins not supported.
if (!getBaseExpression().isExpressionBuilder()) {
throw QueryException.cannotConformExpression();
}
// For bug 2780817 get the mapping directly from the object. In EJB 2.0
// inheritance, each child must override mappings defined in an abstract
// class with its own.
DatabaseMapping mapping = session.getDescriptor(object.getClass()).getObjectBuilder().getMappingForField(getField());
if (mapping == null) {
throw QueryException.cannotConformExpression();
}
return mapping.valueFromObject(object, getField(), session);
}
/**
* INTERNAL:
* Used to print a debug form of the expression tree.
*/
@Override
public void writeDescriptionOn(BufferedWriter writer) throws IOException {
writer.write(getField().toString());
}
/**
* INTERNAL: called from SQLSelectStatement.writeFieldsFromExpression(...)
*/
@Override
public void writeFields(ExpressionSQLPrinter printer, List newFields, SQLSelectStatement statement) {
DatabaseField field = getField();
if (field != null) {
newFields.add(field);
writeField(printer, field, statement);
}
}
@Override
protected void writeField(ExpressionSQLPrinter printer, DatabaseField field, SQLSelectStatement statement) {
if (this.field == field){
//print ", " before each selected field except the first one
if (printer.isFirstElementPrinted()) {
printer.printString(", ");
} else {
printer.setIsFirstElementPrinted(true);
}
printSQL(printer);
} else {
super.writeField(printer, field, statement);
}
}
/**
* INTERNAL:
* writes the field for fine-grained pessimistic locking.
*/
protected void writeForUpdateOf(ExpressionSQLPrinter printer, SQLSelectStatement statement) {
if (printer.getPlatform().shouldPrintAliasForUpdate()) {
writeAlias(printer, field, statement);
} else {
writeField(printer, field, statement);
}
}
}