blob: c95576a18a6a567041391fd6c96a37fd5c3a4b7f [file] [log] [blame]
/*
* Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2006, 2021 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
//
// 04/11/2017-2.6 Will Dazey
// - 512386: Concat expression return type Boolean -> String
// 02/20/2018-2.7 Will Dazey
// - 531062: Incorrect expression type created for CollectionExpression
// 05/11/2018-2.7 Will Dazey
// - 534515: Incorrect return type set for CASE functions
// IBM - Bug 537795: CASE THEN and ELSE scalar expression Constants should not be casted to CASE operand type
package org.eclipse.persistence.internal.jpa.jpql;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.expressions.ExpressionMath;
import org.eclipse.persistence.internal.expressions.ConstantExpression;
import org.eclipse.persistence.internal.expressions.DateConstantExpression;
import org.eclipse.persistence.internal.expressions.MapEntryExpression;
import org.eclipse.persistence.internal.expressions.ParameterExpression;
import org.eclipse.persistence.internal.queries.ReportItem;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.JPQLQueryDeclaration.Type;
import org.eclipse.persistence.jpa.jpql.LiteralType;
import org.eclipse.persistence.jpa.jpql.parser.AbsExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractEclipseLinkExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName;
import org.eclipse.persistence.jpa.jpql.parser.AdditionExpression;
import org.eclipse.persistence.jpa.jpql.parser.AllOrAnyExpression;
import org.eclipse.persistence.jpa.jpql.parser.AndExpression;
import org.eclipse.persistence.jpa.jpql.parser.AnonymousExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticFactor;
import org.eclipse.persistence.jpa.jpql.parser.AsOfClause;
import org.eclipse.persistence.jpa.jpql.parser.AvgFunction;
import org.eclipse.persistence.jpa.jpql.parser.BadExpression;
import org.eclipse.persistence.jpa.jpql.parser.BetweenExpression;
import org.eclipse.persistence.jpa.jpql.parser.CaseExpression;
import org.eclipse.persistence.jpa.jpql.parser.CastExpression;
import org.eclipse.persistence.jpa.jpql.parser.CoalesceExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionValuedPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.ComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConcatExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConnectByClause;
import org.eclipse.persistence.jpa.jpql.parser.ConstructorExpression;
import org.eclipse.persistence.jpa.jpql.parser.CountFunction;
import org.eclipse.persistence.jpa.jpql.parser.DatabaseType;
import org.eclipse.persistence.jpa.jpql.parser.DateTime;
import org.eclipse.persistence.jpa.jpql.parser.DeleteClause;
import org.eclipse.persistence.jpa.jpql.parser.DeleteStatement;
import org.eclipse.persistence.jpa.jpql.parser.DivisionExpression;
import org.eclipse.persistence.jpa.jpql.parser.EclipseLinkAnonymousExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.EclipseLinkExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.EmptyCollectionComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.EntityTypeLiteral;
import org.eclipse.persistence.jpa.jpql.parser.EntryExpression;
import org.eclipse.persistence.jpa.jpql.parser.ExistsExpression;
import org.eclipse.persistence.jpa.jpql.parser.ExtractExpression;
import org.eclipse.persistence.jpa.jpql.parser.FromClause;
import org.eclipse.persistence.jpa.jpql.parser.FunctionExpression;
import org.eclipse.persistence.jpa.jpql.parser.GroupByClause;
import org.eclipse.persistence.jpa.jpql.parser.HavingClause;
import org.eclipse.persistence.jpa.jpql.parser.HierarchicalQueryClause;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.InExpression;
import org.eclipse.persistence.jpa.jpql.parser.IndexExpression;
import org.eclipse.persistence.jpa.jpql.parser.InputParameter;
import org.eclipse.persistence.jpa.jpql.parser.JPQLExpression;
import org.eclipse.persistence.jpa.jpql.parser.Join;
import org.eclipse.persistence.jpa.jpql.parser.KeyExpression;
import org.eclipse.persistence.jpa.jpql.parser.KeywordExpression;
import org.eclipse.persistence.jpa.jpql.parser.LengthExpression;
import org.eclipse.persistence.jpa.jpql.parser.LikeExpression;
import org.eclipse.persistence.jpa.jpql.parser.LocateExpression;
import org.eclipse.persistence.jpa.jpql.parser.LowerExpression;
import org.eclipse.persistence.jpa.jpql.parser.MaxFunction;
import org.eclipse.persistence.jpa.jpql.parser.MinFunction;
import org.eclipse.persistence.jpa.jpql.parser.ModExpression;
import org.eclipse.persistence.jpa.jpql.parser.MultiplicationExpression;
import org.eclipse.persistence.jpa.jpql.parser.NotExpression;
import org.eclipse.persistence.jpa.jpql.parser.NullComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.NullExpression;
import org.eclipse.persistence.jpa.jpql.parser.NullIfExpression;
import org.eclipse.persistence.jpa.jpql.parser.NumericLiteral;
import org.eclipse.persistence.jpa.jpql.parser.ObjectExpression;
import org.eclipse.persistence.jpa.jpql.parser.OnClause;
import org.eclipse.persistence.jpa.jpql.parser.OrExpression;
import org.eclipse.persistence.jpa.jpql.parser.OrderByClause;
import org.eclipse.persistence.jpa.jpql.parser.OrderByItem;
import org.eclipse.persistence.jpa.jpql.parser.OrderSiblingsByClause;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.RegexpExpression;
import org.eclipse.persistence.jpa.jpql.parser.ResultVariable;
import org.eclipse.persistence.jpa.jpql.parser.SelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.SimpleFromClause;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.SizeExpression;
import org.eclipse.persistence.jpa.jpql.parser.SqrtExpression;
import org.eclipse.persistence.jpa.jpql.parser.StartWithClause;
import org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.StringLiteral;
import org.eclipse.persistence.jpa.jpql.parser.SubExpression;
import org.eclipse.persistence.jpa.jpql.parser.SubstringExpression;
import org.eclipse.persistence.jpa.jpql.parser.SubtractionExpression;
import org.eclipse.persistence.jpa.jpql.parser.SumFunction;
import org.eclipse.persistence.jpa.jpql.parser.TableExpression;
import org.eclipse.persistence.jpa.jpql.parser.TableVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.TreatExpression;
import org.eclipse.persistence.jpa.jpql.parser.TrimExpression;
import org.eclipse.persistence.jpa.jpql.parser.TypeExpression;
import org.eclipse.persistence.jpa.jpql.parser.UnionClause;
import org.eclipse.persistence.jpa.jpql.parser.UnknownExpression;
import org.eclipse.persistence.jpa.jpql.parser.UpdateClause;
import org.eclipse.persistence.jpa.jpql.parser.UpdateItem;
import org.eclipse.persistence.jpa.jpql.parser.UpdateStatement;
import org.eclipse.persistence.jpa.jpql.parser.UpperExpression;
import org.eclipse.persistence.jpa.jpql.parser.ValueExpression;
import org.eclipse.persistence.jpa.jpql.parser.WhenClause;
import org.eclipse.persistence.jpa.jpql.parser.WhereClause;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.querykeys.ForeignReferenceQueryKey;
import org.eclipse.persistence.mappings.querykeys.QueryKey;
import org.eclipse.persistence.queries.ReportQuery;
/**
* This {@link org.eclipse.persistence.jpa.jpql.parser.ExpressionVisitor} visits an {@link org.eclipse.persistence.jpa.jpql.parser.Expression
* JPQL Expression} and creates the corresponding {@link org.eclipse.persistence.expressions.Expression EclipseLink Expression}.
*
* @version 2.6
* @since 2.3
* @author Pascal Filion
* @author John Bracken
*/
@SuppressWarnings("nls")
final class ExpressionBuilderVisitor implements EclipseLinkExpressionVisitor {
/**
* This visitor creates a list by retrieving either the single child or the children of the
* {@link CollectionExpression}, which would be the child.
*/
private ChildrenExpressionVisitor childrenExpressionVisitor;
/**
* Determines whether the target relationship is allowed to be <code>null</code>.
*/
private boolean nullAllowed;
/**
* This {@link Comparator} compares two {@link Class} values and returned the appropriate numeric
* type that takes precedence.
*/
private Comparator<Class<?>> numericTypeComparator;
/**
* The context used to query information about the application metadata.
*/
private final JPQLQueryContext queryContext;
/**
* The EclipseLink {@link Expression} that represents a visited parsed
* {@link org.eclipse persistence.jpa.query.parser.Expression Expression}
*/
private Expression queryExpression;
/**
* Keeps track of the type of an expression while traversing it.
*/
private final Class<?>[] type;
/**
* The visitor responsible to create the {@link Expression Expressions} for the <b>WHEN</b> and
* <b>THEN</b> expressions.
*/
private WhenClauseExpressionVisitor whenClauseExpressionVisitor;
/**
* Creates a new <code>ExpressionBuilderVisitor</code>.
*
* @param queryContext The context used to query information about the application metadata and
* cached information
*/
ExpressionBuilderVisitor(JPQLQueryContext queryContext) {
super();
this.type = new Class<?>[1];
this.queryContext = queryContext;
}
private void appendJoinVariables(org.eclipse.persistence.jpa.jpql.parser.Expression expression,
ReportQuery subquery) {
queryExpression = null;
for (String variableName : collectOuterIdentificationVariables()) {
Expression innerExpression = queryContext.getQueryExpression(variableName);
Expression outerExpression = queryContext.getParent().getQueryExpressionImp(variableName);
Expression equalExpression = innerExpression.equal(outerExpression);
if (queryExpression == null) {
queryExpression = equalExpression;
}
else {
queryExpression = queryExpression.and(equalExpression);
}
}
// Aggregate the WHERE clause with the joins expression
if (queryExpression != null) {
Expression whereClause = subquery.getSelectionCriteria();
if (whereClause != null) {
whereClause = whereClause.and(queryExpression);
}
else {
whereClause = queryExpression;
}
subquery.setSelectionCriteria(whereClause);
}
}
/**
* Creates a new EclipseLink {@link Expression} by visiting the given JPQL
* {@link org.eclipse.persistence.jpa.jpql.parser.Expression Expression}.
*
* @param expression The {@link org.eclipse.persistence.jpa.jpql.parser.Expression Expression} to
* convert into an EclipseLink {@link Expression}
* @param type The type of the expression
* @return The EclipseLink {@link Expression} of the JPQL fragment
*/
Expression buildExpression(org.eclipse.persistence.jpa.jpql.parser.Expression expression,
Class<?>[] type) {
Class<?> oldType = this.type[0];
Expression oldQueryExpression = queryExpression;
try {
this.type[0] = null;
this.queryExpression = null;
expression.accept(this);
type[0] = this.type[0];
return queryExpression;
}
finally {
this.type[0] = oldType;
this.queryExpression = oldQueryExpression;
}
}
/**
* Creates a new EclipseLink {@link Expression} by visiting the given JPQL {@link
* CollectionValuedPathExpression} that is used in the <code><b>GROUP BY</b></code> clause.
*
* @param expression The {@link CollectionValuedPathExpression} to convert into an EclipseLink
* {@link Expression}
* @return The EclipseLink {@link Expression} representation of that path expression
*/
Expression buildGroupByExpression(CollectionValuedPathExpression expression) {
try {
PathResolver resolver = new PathResolver();
resolver.length = expression.pathSize() - 1;
resolver.nullAllowed = false;
resolver.checkMappingType = false;
expression.accept(resolver);
return resolver.localExpression;
}
finally {
this.type[0] = null;
this.queryExpression = null;
}
}
/**
* Creates a new EclipseLink {@link Expression} by visiting the given JPQL {@link
* StateFieldPathExpression}. This method temporarily changes the null allowed flag if the state
* field is a foreign reference mapping
*
* @param expression The {@link StateFieldPathExpression} to convert into an EclipseLink {@link
* Expression}
* @return The EclipseLink {@link Expression} representation of that path expression
*/
Expression buildModifiedPathExpression(StateFieldPathExpression expression) {
try {
PathResolver resolver = new PathResolver();
resolver.length = expression.pathSize();
resolver.checkMappingType = true;
expression.accept(resolver);
return resolver.localExpression;
}
finally {
this.type[0] = null;
this.queryExpression = null;
}
}
/**
* Creates a new {@link ReportQuery} by visiting the given {@link SimpleSelectStatement}.
*
* @param expression The {@link SimpleSelectStatement} to convert into a {@link ReportQuery}
* @return A fully initialized {@link ReportQuery}
*/
ReportQuery buildSubquery(SimpleSelectStatement expression) {
// First create the subquery
ReportQuery subquery = new ReportQuery();
queryContext.newSubQueryContext(expression, subquery);
try {
// Visit the subquery to populate it
ReportQueryVisitor visitor = new ReportQueryVisitor(queryContext, subquery);
expression.accept(visitor);
type[0] = visitor.type;
// Add the joins between the subquery and the parent query
appendJoinVariables(expression, subquery);
return subquery;
}
finally {
queryContext.disposeSubqueryContext();
}
}
private List<org.eclipse.persistence.jpa.jpql.parser.Expression> children(org.eclipse.persistence.jpa.jpql.parser.Expression expression) {
ChildrenExpressionVisitor visitor = childrenExpressionVisitor();
try {
expression.accept(visitor);
return new LinkedList<>(visitor.expressions);
}
finally {
visitor.expressions.clear();
}
}
private ChildrenExpressionVisitor childrenExpressionVisitor() {
if (childrenExpressionVisitor == null) {
childrenExpressionVisitor = new ChildrenExpressionVisitor();
}
return childrenExpressionVisitor;
}
private Set<String> collectOuterIdentificationVariables() {
// Retrieve the identification variables used in the current query
Set<String> variableNames = new HashSet<>(queryContext.getUsedIdentificationVariables());
// Now remove the local identification variables that are defined in JOIN expressions and
// in collection member declarations
for (Declaration declaration : queryContext.getDeclarations()) {
variableNames.remove(declaration.getVariableName());
}
return variableNames;
}
@Override
public void visit(AbsExpression expression) {
// First create the expression from the encapsulated expression
expression.getExpression().accept(this);
// Now create the ABS expression
queryExpression = ExpressionMath.abs(queryExpression);
// Note: The type will be calculated when traversing the ABS's expression
}
@Override
public void visit(AbstractSchemaName expression) {
ClassDescriptor descriptor = queryContext.getDescriptor(expression.getText());
type[0] = descriptor.getJavaClass();
queryExpression = new ExpressionBuilder(type[0]);
}
@Override
public void visit(AdditionExpression expression) {
List<Class<?>> types = new ArrayList<>(2);
// Create the left side of the addition expression
expression.getLeftExpression().accept(this);
Expression leftExpression = queryExpression;
types.add(type[0]);
// Create the right side of the addition expression
expression.getRightExpression().accept(this);
Expression rightExpression = queryExpression;
types.add(type[0]);
// Now create the addition expression
queryExpression = ExpressionMath.add(leftExpression, rightExpression);
// Set the expression type
Collections.sort(types, NumericTypeComparator.instance());
type[0] = types.get(0);
}
@Override
public void visit(AllOrAnyExpression expression) {
// First create the subquery
ReportQuery subquery = buildSubquery((SimpleSelectStatement) expression.getExpression());
// Now create the ALL|SOME|ANY expression
String identifier = expression.getIdentifier();
queryExpression = queryContext.getBaseExpression();
if (identifier == AllOrAnyExpression.ALL) {
queryExpression = queryExpression.all(subquery);
}
else if (identifier == AllOrAnyExpression.SOME) {
queryExpression = queryExpression.some(subquery);
}
else if (identifier == AllOrAnyExpression.ANY) {
queryExpression = queryExpression.any(subquery);
}
// Note: The type will be calculated when traversing the ABS's expression
}
@Override
public void visit(AndExpression expression) {
// Create the left side of the logical expression
expression.getLeftExpression().accept(this);
Expression leftExpression = queryExpression;
// Create the right side of the logical expression
expression.getRightExpression().accept(this);
Expression rightExpression = queryExpression;
// Now create the AND expression
queryExpression = leftExpression.and(rightExpression);
// Set the expression type
type[0] = Boolean.class;
}
@Override
public void visit(ArithmeticFactor expression) {
// First create the Expression that is prepended with the unary sign
expression.getExpression().accept(this);
Expression arithmeticFactor = queryExpression;
// Create an expression for the constant 0 (zero)
queryExpression = new ConstantExpression(0, new ExpressionBuilder());
// "- <something>" is really "0 - <something>"
queryExpression = ExpressionMath.subtract(queryExpression, arithmeticFactor);
// Note: The type will be calculated when traversing the sub-expression
}
@Override
public void visit(AsOfClause expression) {
expression.getExpression().accept(this);
}
@Override
public void visit(AvgFunction expression) {
// First create the expression from the encapsulated expression
expression.getExpression().accept(this);
// Mark the AVG expression distinct
if (expression.hasDistinct()) {
queryExpression = queryExpression.distinct();
}
// Now create the AVG expression
queryExpression = queryExpression.average();
// Set the expression type
type[0] = Double.class;
}
@Override
public void visit(BadExpression expression) {
// Nothing to do
}
@Override
public void visit(BetweenExpression expression) {
// First create the Expression for the result expression
expression.getExpression().accept(this);
Expression resultExpression = queryExpression;
// Create the expression for the lower bound expression
expression.getLowerBoundExpression().accept(this);
Expression lowerBoundExpression = queryExpression;
// Create the expression for the upper bound expression
expression.getUpperBoundExpression().accept(this);
Expression upperBoundExpression = queryExpression;
// Create the BETWEEN expression
if (expression.hasNot()) {
queryExpression = resultExpression.notBetween(lowerBoundExpression, upperBoundExpression);
}
else {
queryExpression = resultExpression.between(lowerBoundExpression, upperBoundExpression);
}
// Set the expression type
type[0] = Boolean.class;
}
@Override
public void visit(CaseExpression expression) {
Expression caseBaseExpression = queryExpression;
Expression caseOperandExpression = null;
// Create the case operand expression
if (expression.hasCaseOperand()) {
expression.getCaseOperand().accept(this);
caseOperandExpression = queryExpression;
}
WhenClauseExpressionVisitor visitor = whenClauseExpressionVisitor();
try {
// Create the WHEN clauses
expression.getWhenClauses().accept(visitor);
// Create the ELSE clause
expression.getElseExpression().accept(this);
Expression elseExpression = queryExpression;
visitor.types.add(type[0]);
// Create the CASE expression
if (caseOperandExpression != null) {
queryExpression = caseOperandExpression.caseStatement(visitor.whenClauses, elseExpression);
//Bug 537795
//After we build the caseStatement, we need to retroactively fix the THEN/ELSE children's base
if(queryExpression.isFunctionExpression()) {
Vector<Expression> children = ((org.eclipse.persistence.internal.expressions.FunctionExpression)queryExpression).getChildren();
int index = 1;
while(index < children.size()) {
Expression when_else = children.get(index);
if(index+1 < children.size()) {
//Not at end, must be a THEN
children.get(index+1).setLocalBase(caseBaseExpression);
} else {
//At end, must be an ELSE
when_else.setLocalBase(caseBaseExpression);
}
index=index+2;
}
}
}
else {
queryExpression = queryContext.getBaseExpression();
queryExpression = queryExpression.caseStatement(visitor.whenClauses, elseExpression);
}
// Set the expression type
type[0] = queryContext.typeResolver().compareCollectionEquivalentTypes(visitor.types);
}
finally {
visitor.dispose();
}
}
@Override
public void visit(CastExpression expression) {
// First create the expression from the encapsulated expression
expression.getExpression().accept(this);
// Now create the CAST expression
org.eclipse.persistence.jpa.jpql.parser.Expression databaseType = expression.getDatabaseType();
queryExpression = queryExpression.cast(databaseType.toParsedText());
// Set the expression type
type[0] = Object.class;
}
@Override
public void visit(CoalesceExpression expression) {
List<Expression> expressions = new ArrayList<>();
List<Class<?>> types = new LinkedList<>();
//cache the type of the expression so untyped children have a default type
Class<?> coalesceType = type[0];
// Create the Expression for each scalar expression
for (org.eclipse.persistence.jpa.jpql.parser.Expression child : expression.getExpression().children()) {
child.accept(this);
expressions.add(queryExpression);
//get the expression type parsed from the child expression
Class<?> childType = type[0];
// Default the type on an untyped ParameterExpression to the cached expression type.
// This is to help provide a valid type for null parameter when binding JDBC parameter types
if (queryExpression.isParameterExpression()) {
ParameterExpression paramExpression = (ParameterExpression) queryExpression;
if (paramExpression.getType() == null || paramExpression.getType().equals(Object.class)) {
paramExpression.setType(coalesceType);
childType = coalesceType;
}
}
types.add(childType);
}
// Create the COALESCE expression
queryExpression = queryContext.getBaseExpression();
queryExpression = queryExpression.coalesce(expressions);
// Set the expression type
type[0] = queryContext.typeResolver().compareCollectionEquivalentTypes(types);
}
@Override
public void visit(CollectionExpression expression) {
// Nothing to do, this should be handled by the owning expression
}
@Override
public void visit(CollectionMemberDeclaration expression) {
expression.getCollectionValuedPathExpression().accept(this);
}
@Override
public void visit(CollectionMemberExpression expression) {
// Create the expression for the entity expression
expression.getEntityExpression().accept(this);
Expression entityExpression = queryExpression;
if (expression.hasNot()) {
CollectionValuedPathExpression pathExpression = (CollectionValuedPathExpression) expression.getCollectionValuedPathExpression();
// Retrieve the ExpressionBuilder from the collection-valued path expression's
// identification variable and the variable name
pathExpression.getIdentificationVariable().accept(this);
Expression parentExpression = queryExpression;
// Now create the actual expression
Expression newBuilder = new ExpressionBuilder();
Expression collectionBase = newBuilder;
for (int i = 1; i < pathExpression.pathSize() - 1; i++) {
// nested paths must be single valued.
collectionBase = collectionBase.get(pathExpression.getPath(i));
}
String lastPath = pathExpression.getPath(pathExpression.pathSize() - 1);
// The following code is copied from Expression.noneOf and altered a bit
Expression criteria = newBuilder.equal(parentExpression).and(collectionBase.anyOf(lastPath).equal(entityExpression));
ReportQuery subQuery = new ReportQuery();
subQuery.setShouldRetrieveFirstPrimaryKey(true);
subQuery.setSelectionCriteria(criteria);
// subQuery has the same reference class as parentExpression (which is an ExpressionBuilder).
subQuery.setReferenceClass(((ExpressionBuilder)parentExpression).getQueryClass());
queryExpression = parentExpression.notExists(subQuery);
}
else {
// Create the expression for the collection-valued path expression
expression.getCollectionValuedPathExpression().accept(this);
// Create the MEMBER OF expression
queryExpression = queryExpression.equal(entityExpression);
}
}
@Override
public void visit(CollectionValuedPathExpression expression) {
visitPathExpression(expression, nullAllowed, expression.pathSize());
}
@Override
public void visit(ComparisonExpression expression) {
ComparisonExpressionVisitor visitor = new ComparisonExpressionVisitor();
// Create the left side of the comparison expression
expression.getLeftExpression().accept(visitor);
Expression leftExpression = queryExpression;
// Create the right side of the comparison expression
expression.getRightExpression().accept(visitor);
Expression rightExpression = queryExpression;
// Now create the comparison expression
String comparaison = expression.getComparisonOperator();
// =
if (comparaison == ComparisonExpression.EQUAL) {
queryExpression = leftExpression.equal(rightExpression);
}
// <>, !=
else if (comparaison == ComparisonExpression.DIFFERENT ||
comparaison == ComparisonExpression.NOT_EQUAL) {
queryExpression = leftExpression.notEqual(rightExpression);
}
// <
else if (comparaison == ComparisonExpression.LOWER_THAN) {
queryExpression = leftExpression.lessThan(rightExpression);
}
// <=
else if (comparaison == ComparisonExpression.LOWER_THAN_OR_EQUAL) {
queryExpression = leftExpression.lessThanEqual(rightExpression);
}
// >
else if (comparaison == ComparisonExpression.GREATER_THAN) {
queryExpression = leftExpression.greaterThan(rightExpression);
}
// <=
else if (comparaison == ComparisonExpression.GREATER_THAN_OR_EQUAL) {
queryExpression = leftExpression.greaterThanEqual(rightExpression);
}
else {
throw new IllegalArgumentException("The comparison operator is unknown: " + comparaison);
}
// Set the expression type
type[0] = Boolean.class;
}
@Override
public void visit(ConcatExpression expression) {
List<org.eclipse.persistence.jpa.jpql.parser.Expression> expressions = children(expression.getExpression());
Expression newExpression = null;
for (org.eclipse.persistence.jpa.jpql.parser.Expression child : expressions) {
child.accept(this);
if (newExpression == null) {
newExpression = queryExpression;
}
else {
newExpression = newExpression.concat(queryExpression);
}
}
queryExpression = newExpression;
// Set the expression type
type[0] = String.class;
}
@Override
public void visit(ConnectByClause expression) {
expression.getExpression().accept(this);
}
@Override
public void visit(ConstructorExpression expression) {
// Nothing to do
}
@Override
public void visit(CountFunction expression) {
// Create the expression from the encapsulated expression
expression.getExpression().accept(this);
// Mark the expression has distinct
if (expression.hasDistinct()) {
queryExpression = queryExpression.distinct();
}
// Create the COUNT expression
queryExpression = queryExpression.count();
// Set the expression type
type[0] = Long.class;
}
@Override
public void visit(DatabaseType expression) {
// Nothing to do
}
@Override
public void visit(DateTime expression) {
if (expression.isJDBCDate()) {
queryExpression = queryContext.getBaseExpression();
queryExpression = new DateConstantExpression(expression.getText(), queryExpression);
String text = expression.getText();
if (text.startsWith("{d")) {
type[0] = Date.class;
}
else if (text.startsWith("{ts")) {
type[0] = Timestamp.class;
}
else if (text.startsWith("{t")) {
type[0] = Time.class;
}
else {
type[0] = Object.class;
}
}
else {
queryExpression = queryContext.getBaseExpression();
if (expression.isCurrentDate()) {
queryExpression = queryExpression.currentDateDate();
type[0] = Date.class;
}
else if (expression.isCurrentTime()) {
queryExpression = queryExpression.currentTime();
type[0] = Time.class;
}
else if (expression.isCurrentTimestamp()) {
queryExpression = queryExpression.currentTimeStamp();
type[0] = Timestamp.class;
}
else {
throw new IllegalArgumentException("The DateTime is unknown: " + expression);
}
}
}
@Override
public void visit(DeleteClause expression) {
// Nothing to do
}
@Override
public void visit(DeleteStatement expression) {
// Nothing to do
}
@Override
public void visit(DivisionExpression expression) {
List<Class<?>> types = new ArrayList<>(2);
// Create the left side of the division expression
expression.getLeftExpression().accept(this);
Expression leftExpression = queryExpression;
types.add(type[0]);
// Create the right side of the division expression
expression.getRightExpression().accept(this);
Expression rightExpression = queryExpression;
types.add(type[0]);
// Now create the division expression
queryExpression = ExpressionMath.divide(leftExpression, rightExpression);
// Set the expression type
Collections.sort(types, NumericTypeComparator.instance());
type[0] = types.get(0);
}
@Override
public void visit(EmptyCollectionComparisonExpression expression) {
CollectionValuedPathExpression collectionPath = (CollectionValuedPathExpression) expression.getExpression();
int lastPathIndex = collectionPath.pathSize() - 1;
String name = collectionPath.getPath(lastPathIndex);
// Create the expression for the collection-valued path expression (except the last path)
visitPathExpression(collectionPath, false, lastPathIndex);
// Create the IS EMPTY expression
if (expression.hasNot()) {
queryExpression = queryExpression.notEmpty(name);
}
else {
queryExpression = queryExpression.isEmpty(name);
}
// Set the expression type
type[0] = Boolean.class;
}
@Override
public void visit(EntityTypeLiteral expression) {
ClassDescriptor descriptor = queryContext.getDescriptor(expression.getEntityTypeName());
type[0] = descriptor.getJavaClass();
queryExpression = new ConstantExpression(type[0], queryContext.getBaseExpression());
}
@Override
public void visit(EntryExpression expression) {
// Create the expression for the collection-valued path expression
expression.getExpression().accept(this);
// Now create the ENTRY expression
MapEntryExpression entryExpression = new MapEntryExpression(queryExpression);
entryExpression.returnMapEntry();
queryExpression = entryExpression;
// Set the expression type
type[0] = Map.Entry.class;
}
@Override
public void visit(ExistsExpression expression) {
// First create the subquery
ReportQuery subquery = buildSubquery((SimpleSelectStatement) expression.getExpression());
// Replace the SELECT clause of the exists subquery by SELECT 1 to avoid problems with
// databases not supporting multiple columns in the subquery SELECT clause in SQL. The
// original select clause expressions might include relationship navigations which should
// result in FK joins in the generated SQL, e.g. ... EXISTS (SELECT o.customer FROM Order
// o ...). Add the select clause expressions as non fetch join attributes to the ReportQuery
// representing the subquery. This make sure the FK joins get generated
for (ReportItem item : subquery.getItems()) {
Expression expr = item.getAttributeExpression();
subquery.addNonFetchJoinedAttribute(expr);
}
subquery.clearItems();
Expression one = new ConstantExpression(1, new ExpressionBuilder());
subquery.addItem("one", one);
subquery.dontUseDistinct();
// Now create the EXISTS expression
queryExpression = queryContext.getBaseExpression();
if (expression.hasNot()) {
queryExpression = queryExpression.notExists(subquery);
}
else {
queryExpression = queryExpression.exists(subquery);
}
// Set the expression type
type[0] = Boolean.class;
}
@Override
public void visit(ExtractExpression expression) {
// First create the expression from the encapsulated expression
expression.getExpression().accept(this);
// Now create the EXTRACT expression
queryExpression = queryExpression.extract(expression.getDatePart());
// Set the expression type
type[0] = Object.class;
}
@Override
public void visit(FromClause expression) {
// Nothing to do
}
@Override
public void visit(FunctionExpression expression) {
String identifier = expression.getIdentifier();
String functionName = expression.getUnquotedFunctionName();
// COLUMN
if (identifier == org.eclipse.persistence.jpa.jpql.parser.Expression.COLUMN) {
// Create the expression for the single child
expression.getExpression().accept(this);
// Create the expression representing a field in a data-level query
queryExpression = queryExpression.getField(functionName);
}
else {
List<org.eclipse.persistence.jpa.jpql.parser.Expression> expressions = children(expression.getExpression());
// No arguments
if (expressions.isEmpty()) {
queryExpression = queryContext.getBaseExpression();
// OPERATOR
if (identifier == org.eclipse.persistence.jpa.jpql.parser.Expression.SQL) {
queryExpression = queryExpression.literal(functionName);
}
// FUNC/FUNCTION
else {
queryExpression = queryExpression.getFunction(functionName);
}
}
// One or more arguments
else {
// Create the Expressions for the rest
List<Expression> queryExpressions = new ArrayList<>(expressions.size());
for (org.eclipse.persistence.jpa.jpql.parser.Expression child : expressions) {
child.accept(this);
queryExpressions.add(queryExpression);
}
queryExpression = queryExpressions.remove(0);
// SQL
if (identifier == org.eclipse.persistence.jpa.jpql.parser.Expression.SQL) {
queryExpression = queryExpression.sql(functionName, queryExpressions);
}
// OPERATOR
else if (identifier == org.eclipse.persistence.jpa.jpql.parser.Expression.OPERATOR) {
queryExpression = queryExpression.operator(functionName, queryExpressions);
}
// FUNC/FUNCTION
else {
queryExpression = queryExpression.getFunctionWithArguments(functionName, queryExpressions);
}
}
}
// Set the expression type
type[0] = Object.class;
}
@Override
public void visit(GroupByClause expression) {
// Nothing to do
}
@Override
public void visit(HavingClause expression) {
expression.getConditionalExpression().accept(this);
}
@Override
public void visit(HierarchicalQueryClause expression) {
// Nothing to do
}
@Override
public void visit(IdentificationVariable expression) {
// The identification variable is virtual, only do something
// if it falsely represents a state field path expression
if (expression.isVirtual()) {
StateFieldPathExpression stateFieldPathExpression = expression.getStateFieldPathExpression();
if (stateFieldPathExpression != null) {
stateFieldPathExpression.accept(this);
return;
}
}
String variableName = expression.getVariableName();
// Identification variable, it's important to use findQueryExpression() and not
// getQueryExpression(). If the identification variable is defined by the parent
// query, then the ExpressionBuilder have most likely been created already
queryExpression = queryContext.findQueryExpression(variableName);
// Retrieve the Declaration mapped to the variable name
Declaration declaration = queryContext.findDeclaration(variableName);
// A null Declaration would most likely mean it's coming from a
// state field path expression that represents an enum constant
if (declaration != null) {
// The Expression was not created yet, which can happen if the identification
// variable is declared in a parent query. If that is the case, create the
// ExpressionBuilder and cache it for the current query
if (queryExpression == null) {
declaration.getBaseExpression().accept(this);
queryContext.addQueryExpression(variableName, queryExpression);
}
// Retrieve the Entity type
if (declaration.getType() == Type.RANGE) {
type[0] = declaration.getDescriptor().getJavaClass();
}
}
}
@Override
public void visit(IdentificationVariableDeclaration expression) {
// Nothing to do
}
@Override
public void visit(IndexExpression expression) {
// Create the expression for the encapsulated expression
expression.getExpression().accept(this);
// Now create the INDEX expression
queryExpression = queryExpression.index();
// Set the expression type
type[0] = Integer.class;
}
@Override
public void visit(InExpression expression) {
// Visit the left expression
InExpressionExpressionBuilder visitor1 = new InExpressionExpressionBuilder();
expression.getExpression().accept(visitor1);
// Visit the IN items
InExpressionBuilder visitor2 = new InExpressionBuilder();
visitor2.hasNot = expression.hasNot();
visitor2.singleInputParameter = expression.isSingleInputParameter();
visitor2.leftExpression = queryExpression;
expression.getInItems().accept(visitor2);
// Set the expression type
type[0] = Boolean.class;
}
@Override
public void visit(InputParameter expression) {
String parameterName = expression.getParameter();
// Calculate the input parameter type
type[0] = queryContext.getParameterType(expression);
// Create the expression
queryExpression = queryContext.getBaseExpression();
queryExpression = queryExpression.getParameter(parameterName.substring(1), type[0]);
// Cache the input parameter type
queryContext.addInputParameter(expression, queryExpression);
}
@Override
public void visit(Join expression) {
try {
nullAllowed = expression.isLeftJoin();
expression.getJoinAssociationPath().accept(this);
}
finally {
nullAllowed = false;
}
}
@Override
public void visit(JPQLExpression expression) {
// Nothing to do
}
@Override
public void visit(KeyExpression expression) {
// First visit the parent Expression
expression.getExpression().accept(this);
// Now create the Expression of the KEY expression
queryExpression = new MapEntryExpression(queryExpression);
}
@Override
public void visit(KeywordExpression expression) {
String keyword = expression.getText();
Object value;
if (keyword == KeywordExpression.NULL) {
value = null;
type[0] = Object.class;
}
else if (keyword == KeywordExpression.TRUE) {
value = Boolean.TRUE;
type[0] = Boolean.class;
}
else {
value = Boolean.FALSE;
type[0] = Boolean.class;
}
queryExpression = queryContext.getBaseExpression();
queryExpression = new ConstantExpression(value, queryExpression);
}
@Override
public void visit(LengthExpression expression) {
// Create the expression from the encapsulated expression
expression.getExpression().accept(this);
// Now create the LENGTH expression
queryExpression = queryExpression.length();
// Set the expression type
type[0] = Integer.class;
}
@Override
public void visit(LikeExpression expression) {
// Create the first expression
expression.getStringExpression().accept(this);
Expression firstExpression = queryExpression;
// Create the expression for the pattern value
expression.getPatternValue().accept(this);
Expression patternValue = queryExpression;
// Create the LIKE expression with the escape character
if (expression.hasEscapeCharacter()) {
expression.getEscapeCharacter().accept(this);
queryExpression = firstExpression.like(patternValue, queryExpression);
}
// Create the LIKE expression with no escape character
else {
queryExpression = firstExpression.like(patternValue);
}
// Negate the expression
if (expression.hasNot()) {
queryExpression = queryExpression.not();
}
// Set the expression type
type[0] = Boolean.class;
}
@Override
public void visit(LocateExpression expression) {
// Create the string to find in the find in expression
expression.getFirstExpression().accept(this);
Expression findExpression = queryExpression;
// Create the find in string expression
expression.getSecondExpression().accept(this);
Expression findInExpression = queryExpression;
// Create the expression for the start position
expression.getThirdExpression().accept(this);
Expression startPositionExpression = queryExpression;
// Create the LOCATE expression
if (startPositionExpression != null) {
queryExpression = findInExpression.locate(findExpression, startPositionExpression);
}
else {
queryExpression = findInExpression.locate(findExpression);
}
// Set the expression type
type[0] = Integer.class;
}
@Override
public void visit(LowerExpression expression) {
// Create the expression from the encapsulated expression
expression.getExpression().accept(this);
// Now create the LOWER expression
queryExpression = queryExpression.toLowerCase();
// Set the expression type
type[0] = String.class;
}
@Override
public void visit(MaxFunction expression) {
// Create the expression from the encapsulated expression
expression.getExpression().accept(this);
// Mark the MAX expression has distinct
if (expression.hasDistinct()) {
queryExpression = queryExpression.distinct();
}
// Now create the MAX expression
queryExpression = queryExpression.maximum();
// Note: The type will be calculated when traversing the sub-expression
}
@Override
public void visit(MinFunction expression) {
// Create the expression from the encapsulated expression
expression.getExpression().accept(this);
// Mark the MIN expression has distinct
if (expression.hasDistinct()) {
queryExpression = queryExpression.distinct();
}
// Now create the MIN expression
queryExpression = queryExpression.minimum();
// Note: The type will be calculated when traversing the sub-expression
}
@Override
public void visit(ModExpression expression) {
// First create the Expression for the first expression
expression.getFirstExpression().accept(this);
Expression leftExpression = queryExpression;
// Now create the Expression for the second expression
expression.getSecondExpression().accept(this);
Expression rightExpression = queryExpression;
// Now create the MOD expression
queryExpression = ExpressionMath.mod(leftExpression, rightExpression);
// Set the expression type
type[0] = Integer.class;
}
@Override
public void visit(MultiplicationExpression expression) {
List<Class<?>> types = new ArrayList<>(2);
// Create the left side of the multiplication expression
expression.getLeftExpression().accept(this);
Expression leftExpression = queryExpression;
types.add(type[0]);
// Create the right side of the multiplication expression
expression.getRightExpression().accept(this);
Expression rightExpression = queryExpression;
types.add(type[0]);
// Now create the multiplication expression
queryExpression = ExpressionMath.multiply(leftExpression, rightExpression);
// Set the expression type
Collections.sort(types, NumericTypeComparator.instance());
type[0] = types.get(0);
}
@Override
public void visit(NotExpression expression) {
// Create the expression
expression.getExpression().accept(this);
// Negate the expression
queryExpression = queryExpression.not();
// Set the expression type
type[0] = Boolean.class;
}
@Override
public void visit(NullComparisonExpression expression) {
// Create the expression first
expression.getExpression().accept(this);
// Mark it as NOT NULL
if (expression.hasNot()) {
queryExpression = queryExpression.notNull();
}
// Mark it as IS NULL
else {
queryExpression = queryExpression.isNull();
}
// Set the expression type
type[0] = Boolean.class;
}
@Override
public void visit(NullExpression expression) {
queryExpression = null;
type[0] = null;
}
@Override
public void visit(NullIfExpression expression) {
// Create the first expression
expression.getFirstExpression().accept(this);
Expression firstExpression = queryExpression;
Class<?> actualType = type[0];
// Create the second expression
expression.getSecondExpression().accept(this);
Expression secondExpression = queryExpression;
// Now create the NULLIF expression
queryExpression = firstExpression.nullIf(secondExpression);
// Set the expression type
type[0] = actualType;
}
@Override
public void visit(NumericLiteral expression) {
// Instantiate a Number object with the value
type[0] = queryContext.getType(expression);
// Special case for a long number, Long.parseLong() does not handle 'l|L'
// but Double.parseDouble() and Float.parseFloat() do handle 'd|D' and 'f|F', respectively
String text = expression.getText();
if ((type[0] == Long.class) && (text.endsWith("L") || text.endsWith("l"))) {
text = text.substring(0, text.length() - 1);
}
@SuppressWarnings({"unchecked"})
Number number = queryContext.newInstance((Class<? extends Number>) type[0], String.class, text);
// Now create the numeric expression
queryExpression = new ConstantExpression(number, queryContext.getBaseExpression());
}
@Override
public void visit(ObjectExpression expression) {
// Simply traverse the OBJECT's expression
expression.getExpression().accept(this);
}
@Override
public void visit(OnClause expression) {
expression.getConditionalExpression().accept(this);
}
@Override
public void visit(OrderByClause expression) {
// Nothing to do
}
@Override
public void visit(OrderByItem expression) {
// Create the item
expression.getExpression().accept(this);
// Create the ordering item
switch (expression.getOrdering()) {
case ASC: queryExpression = queryExpression.ascending(); break;
case DESC: queryExpression = queryExpression.descending(); break;
}
// Create the null ordering item
switch (expression.getNullOrdering()) {
case NULLS_FIRST: queryExpression = queryExpression.nullsFirst(); break;
case NULLS_LAST: queryExpression = queryExpression.nullsLast(); break;
}
}
@Override
public void visit(OrderSiblingsByClause expression) {
expression.getOrderByItems().accept(this);
}
@Override
public void visit(OrExpression expression) {
// Create the left side of the logical expression
expression.getLeftExpression().accept(this);
Expression leftExpression = queryExpression;
// Create the right side of the logical expression
expression.getRightExpression().accept(this);
Expression rightExpression = queryExpression;
// Now create the OR expression
queryExpression = leftExpression.or(rightExpression);
// Set the expression type
type[0] = Boolean.class;
}
@Override
public void visit(RangeVariableDeclaration expression) {
IdentificationVariable variable = (IdentificationVariable) expression.getIdentificationVariable();
Declaration declaration = queryContext.getDeclaration(variable.getVariableName());
switch (declaration.getType()) {
// If the Declaration is RangeDeclaration, then retrieve its Descriptor directly,
// this will support two cases automatically, the "root" object is
// 1) An abstract schema name (entity name) -> parsed as AbstractSchemaName
// 2) A fully qualified class name -> parsed as a CollectionValuedPathExpression
// that cannot be visited
case RANGE:
case CLASS_NAME: {
type[0] = declaration.getDescriptor().getJavaClass();
queryExpression = new ExpressionBuilder(type[0]);
break;
}
// The FROM subquery needs to be created differently than a regular subquery
case SUBQUERY: {
type[0] = null;
queryExpression = declaration.getQueryExpression();
break;
}
// This should be a derived path (CollectionValuedPathExpression) or a subquery
default: {
expression.getRootObject().accept(this);
break;
}
}
}
@Override
public void visit(RegexpExpression expression) {
// Create the first expression
expression.getStringExpression().accept(this);
Expression firstExpression = queryExpression;
// Create the expression for the pattern value
expression.getPatternValue().accept(this);
Expression patternValue = queryExpression;
// Create the REGEXP expression
queryExpression = firstExpression.regexp(patternValue);
// Set the expression type
type[0] = Boolean.class;
}
@Override
public void visit(ResultVariable expression) {
expression.getSelectExpression().accept(this);
IdentificationVariable identificationVariable = (IdentificationVariable) expression.getResultVariable();
String variableName = identificationVariable.getVariableName();
queryContext.addQueryExpression(variableName, queryExpression);
// Note: The type will be calculated when traversing the select expression
}
@Override
public void visit(SelectClause expression) {
// Nothing to do
}
@Override
public void visit(SelectStatement expression) {
// Nothing to do
}
@Override
public void visit(SimpleFromClause expression) {
// Nothing to do
}
@Override
public void visit(SimpleSelectClause expression) {
// Nothing to do
}
@Override
public void visit(SimpleSelectStatement expression) {
// First create the subquery
ReportQuery subquery = buildSubquery(expression);
// Now wrap the subquery
queryExpression = queryContext.getBaseExpression();
queryExpression = queryExpression.subQuery(subquery);
}
@Override
public void visit(SizeExpression expression) {
CollectionValuedPathExpression pathExpression = (CollectionValuedPathExpression) expression.getExpression();
int lastIndex = pathExpression.pathSize() - 1;
// Create the right chain of expressions
visitPathExpression(pathExpression, false, lastIndex - 1);
// Now create the SIZE expression
String name = pathExpression.getPath(lastIndex);
queryExpression = queryExpression.size(name);
// Set the expression type
type[0] = Integer.class;
}
@Override
public void visit(SqrtExpression expression) {
// First create the expression from the encapsulated expression
expression.getExpression().accept(this);
// Now create the SQRT expression
queryExpression = ExpressionMath.sqrt(queryExpression);
// Set the expression type
type[0] = Double.class;
}
@Override
public void visit(StartWithClause expression) {
expression.getConditionalExpression().accept(this);
}
@Override
public void visit(StateFieldPathExpression expression) {
visitPathExpression(expression, false, expression.pathSize());
}
@Override
public void visit(StringLiteral expression) {
// Create the expression
queryExpression = queryContext.getBaseExpression();
queryExpression = new ConstantExpression(expression.getUnquotedText(), queryExpression);
// Set the expression type
type[0] = String.class;
}
@Override
public void visit(SubExpression expression) {
expression.getExpression().accept(this);
}
@Override
public void visit(SubstringExpression expression) {
// Create the first expression
expression.getFirstExpression().accept(this);
Expression firstExpression = queryExpression;
// Create the second expression
expression.getSecondExpression().accept(this);
Expression secondExpression = queryExpression;
// Create the third expression
expression.getThirdExpression().accept(this);
Expression thirdExpression = queryExpression;
// Now create the SUBSTRING expression
if (thirdExpression != null) {
queryExpression = firstExpression.substring(secondExpression, thirdExpression);
}
else {
queryExpression = firstExpression.substring(secondExpression);
}
// Set the expression type
type[0] = String.class;
}
@Override
public void visit(SubtractionExpression expression) {
List<Class<?>> types = new ArrayList<>(2);
// Create the left side of the subtraction expression
expression.getLeftExpression().accept(this);
Expression leftExpression = queryExpression;
types.add(type[0]);
// Create the right side of the subtraction expression
expression.getRightExpression().accept(this);
Expression rightExpression = queryExpression;
types.add(type[0]);
// Now create the subtraction expression
queryExpression = ExpressionMath.subtract(leftExpression, rightExpression);
// Set the expression type
Collections.sort(types, NumericTypeComparator.instance());
type[0] = types.get(0);
}
@Override
public void visit(SumFunction expression) {
// First create the expression from the encapsulated expression
expression.getExpression().accept(this);
// Mark the SUM expression distinct
if (expression.hasDistinct()) {
queryExpression = queryExpression.distinct();
}
// Now create the SUM expression
queryExpression = queryExpression.sum();
// Set the expression type
type[0] = queryContext.typeResolver().convertSumFunctionType(type[0]);
}
@Override
public void visit(TableExpression expression) {
String tableName = queryContext.literal(expression.getExpression(), LiteralType.STRING_LITERAL);
tableName = ExpressionTools.unquote(tableName);
queryExpression = queryContext.getBaseExpression().getTable(tableName);
}
@Override
public void visit(TableVariableDeclaration expression) {
// Nothing to do
}
@Override
public void visit(TreatExpression expression) {
// First visit the parent Expression
expression.getCollectionValuedPathExpression().accept(this);
// Now downcast the Expression
EntityTypeLiteral entityTypeLiteral = (EntityTypeLiteral) expression.getEntityType();
ClassDescriptor entityType = queryContext.getDescriptor(entityTypeLiteral.getEntityTypeName());
queryExpression = queryExpression.treat(entityType.getJavaClass());
}
@Override
public void visit(TrimExpression expression) {
// Create the TRIM character expression
expression.getTrimCharacter().accept(this);
Expression trimCharacter = queryExpression;
// Create the string to trim
expression.getExpression().accept(this);
Expression stringExpression = queryExpression;
switch (expression.getSpecification()) {
case LEADING: {
if (trimCharacter != null) {
queryExpression = stringExpression.leftTrim(trimCharacter);
}
else {
queryExpression = stringExpression.leftTrim();
}
break;
}
case TRAILING: {
if (trimCharacter != null) {
queryExpression = stringExpression.rightTrim(trimCharacter);
}
else {
queryExpression = stringExpression.rightTrim();
}
break;
}
default: {
if (trimCharacter != null) {
queryExpression = stringExpression.trim(trimCharacter);
}
else {
queryExpression = stringExpression.trim();
}
break;
}
}
// Set the expression type
type[0] = String.class;
}
@Override
public void visit(TypeExpression expression) {
// First create the Expression for the identification variable
expression.getExpression().accept(this);
// Create the TYPE expression
queryExpression = queryExpression.type();
// Note: The type will be calculated when traversing the select expression
}
@Override
public void visit(UnionClause expression) {
// Nothing to do
}
@Override
public void visit(UnknownExpression expression) {
queryExpression = null;
}
@Override
public void visit(UpdateClause expression) {
// Nothing to do
}
@Override
public void visit(UpdateItem expression) {
// Nothing to do
}
@Override
public void visit(UpdateStatement expression) {
// Nothing to do
}
@Override
public void visit(UpperExpression expression) {
// First create the expression from the encapsulated expression
expression.getExpression().accept(this);
// Now create the UPPER expression
queryExpression = queryExpression.toUpperCase();
// Set the expression type
type[0] = String.class;
}
@Override
public void visit(ValueExpression expression) {
expression.getExpression().accept(this);
}
@Override
public void visit(WhenClause expression) {
// Nothing to do
}
@Override
public void visit(WhereClause expression) {
expression.getConditionalExpression().accept(this);
}
private void visitPathExpression(AbstractPathExpression expression,
boolean nullAllowed,
int lastIndex) {
PathResolver resolver = new PathResolver();
resolver.length = lastIndex;
resolver.nullAllowed = nullAllowed;
resolver.checkMappingType = false;
resolver.localExpression = null;
resolver.descriptor = null;
expression.accept(resolver);
queryExpression = resolver.localExpression;
}
private WhenClauseExpressionVisitor whenClauseExpressionVisitor() {
if (whenClauseExpressionVisitor == null) {
whenClauseExpressionVisitor = new WhenClauseExpressionVisitor();
}
return whenClauseExpressionVisitor;
}
// Made static for performance reasons.
/**
* This visitor creates a list by retrieving either the single child or the children of the
* {@link CollectionExpression}, which would be the child.
*/
private static class ChildrenExpressionVisitor extends AnonymousExpressionVisitor {
/**
* The list of {@link org.eclipse.persistence.jpa.jpql.parser.Expression Expression} that are
* the children of an expression.
*/
List<org.eclipse.persistence.jpa.jpql.parser.Expression> expressions;
/**
* Creates a new <code>ChildrenExpressionVisitor</code>.
*/
ChildrenExpressionVisitor() {
super();
expressions = new ArrayList<>();
}
@Override
public void visit(CollectionExpression expression) {
for (org.eclipse.persistence.jpa.jpql.parser.Expression child : expression.children()) {
expressions.add(child);
}
}
@Override
public void visit(NullExpression expression) {
// Can't be added to the list
}
@Override
protected void visit(org.eclipse.persistence.jpa.jpql.parser.Expression expression) {
expressions.add(expression);
}
}
/**
* This visitor makes sure to properly handle an entity type literal being parsed as an
* identification variable.
*/
private class ComparisonExpressionVisitor extends EclipseLinkAnonymousExpressionVisitor {
@Override
public void visit(IdentificationVariable expression) {
boolean found = false;
if (!expression.isVirtual()) {
ClassDescriptor descriptor = queryContext.getDescriptor(expression.getText());
// Entity type name
if (descriptor != null) {
type[0] = descriptor.getJavaClass();
queryExpression = new ConstantExpression(type[0], queryContext.getBaseExpression());
found = true;
}
}
if (!found) {
expression.accept(ExpressionBuilderVisitor.this);
}
}
@Override
protected void visit(org.eclipse.persistence.jpa.jpql.parser.Expression expression) {
expression.accept(ExpressionBuilderVisitor.this);
}
}
/**
* This visitor takes care of creating the left expression of an <code><b>IN</b></code> expression
* and make sure to support nested arrays.
*/
private class InExpressionExpressionBuilder extends EclipseLinkAnonymousExpressionVisitor {
@Override
public void visit(CollectionExpression expression) {
// Assume this is a nested array
List<Expression> children = new LinkedList<>();
for (org.eclipse.persistence.jpa.jpql.parser.Expression child : expression.children()) {
child.accept(this);
children.add(queryExpression);
}
queryExpression = new org.eclipse.persistence.internal.expressions.CollectionExpression(children, queryContext.getBaseExpression());
}
@Override
public void visit(org.eclipse.persistence.jpa.jpql.parser.Expression expression) {
expression.accept(ExpressionBuilderVisitor.this);
}
@Override
public void visit(SubExpression expression) {
expression.getExpression().accept(this);
}
}
/**
* This visitor takes care of creating the <code><b>IN</b></code> expression by visiting the items.
*/
private class InExpressionBuilder extends EclipseLinkAnonymousExpressionVisitor {
private boolean hasNot;
private Expression leftExpression;
private boolean singleInputParameter;
/**
* Creates a new <code>InExpressionBuilder</code>.
*/
InExpressionBuilder() {
super();
}
@Override
public void visit(CollectionExpression expression) {
Collection<Expression> expressions = new ArrayList<>();
InItemExpressionVisitor visitor = new InItemExpressionVisitor();
for (org.eclipse.persistence.jpa.jpql.parser.Expression child : expression.children()) {
child.accept(visitor);
expressions.add(queryExpression);
}
if (hasNot) {
queryExpression = leftExpression.notIn(expressions);
}
else {
queryExpression = leftExpression.in(expressions);
}
}
@Override
public void visit(IdentificationVariable expression) {
boolean found = false;
if (!expression.isVirtual()) {
ClassDescriptor descriptor = queryContext.getDescriptor(expression.getText());
// Entity type name
if (descriptor != null) {
type[0] = descriptor.getJavaClass();
queryExpression = new ConstantExpression(type[0], queryContext.getBaseExpression());
found = true;
}
}
if (!found) {
expression.accept(ExpressionBuilderVisitor.this);
}
}
@Override
public void visit(InputParameter expression) {
if (singleInputParameter) {
String parameterName = expression.getParameter();
// Create the expression with Collection as the default type
queryExpression = queryContext.getBaseExpression();
queryExpression = queryExpression.getParameter(parameterName.substring(1), Collection.class);
// Cache the input parameter type, which is by default Collection
queryContext.addInputParameter(expression, queryExpression);
if (hasNot) {
queryExpression = leftExpression.notIn(queryExpression);
}
else {
queryExpression = leftExpression.in(queryExpression);
}
}
else {
visit((org.eclipse.persistence.jpa.jpql.parser.Expression) expression);
}
}
@Override
protected void visit(org.eclipse.persistence.jpa.jpql.parser.Expression expression) {
expression.accept(ExpressionBuilderVisitor.this);
Collection<Expression> expressions = new ArrayList<>();
expressions.add(queryExpression);
// Now create the IN expression
if (hasNot) {
queryExpression = leftExpression.notIn(expressions);
}
else {
queryExpression = leftExpression.in(expressions);
}
}
@Override
public void visit(SimpleSelectStatement expression) {
// First create the subquery
ReportQuery subquery = buildSubquery(expression);
// Now create the IN expression
if (hasNot) {
queryExpression = leftExpression.notIn(subquery);
}
else {
queryExpression = leftExpression.in(subquery);
}
}
private class InItemExpressionVisitor extends AnonymousExpressionVisitor {
@Override
public void visit(IdentificationVariable expression) {
ClassDescriptor descriptor = queryContext.getDescriptor(expression.getVariableName());
queryExpression = queryContext.getBaseExpression();
queryExpression = new ConstantExpression(descriptor.getJavaClass(), queryExpression);
}
@Override
public void visit(CollectionExpression expression) {
// Assume this is a nested array
List<Expression> children = new LinkedList<>();
for (org.eclipse.persistence.jpa.jpql.parser.Expression child : expression.children()) {
child.accept(this);
children.add(queryExpression);
}
queryExpression = new org.eclipse.persistence.internal.expressions.CollectionExpression(children, queryContext.getBaseExpression());
}
@Override
public void visit(SubExpression expression) {
expression.getExpression().accept(this);
}
@Override
protected void visit(org.eclipse.persistence.jpa.jpql.parser.Expression expression) {
expression.accept(ExpressionBuilderVisitor.this);
}
}
}
private class PathResolver extends AbstractEclipseLinkExpressionVisitor {
/**
* Determines whether
*/
boolean checkMappingType;
/**
* Keeps track of the {@link Declaration} when the identification variable maps to a "root"
* object defined in the <code><b>FROM</b></code> clause.
*/
private Declaration declaration;
/**
* Keeps track of the descriptor while traversing the path expression.
*/
private ClassDescriptor descriptor;
/**
* The actual number of paths within the path expression that will be traversed in order to
* create the EclipseLink {@link Expression}.
*/
int length;
/**
* The EclipseLink {@link Expression} that was retrieved or created while traversing the path
* expression.
*/
Expression localExpression;
/**
* Determines whether the target relationship is allowed to be <code>null</code>.
*/
boolean nullAllowed;
/**
* Resolves a database column.
*
* @param expression The path expression representing an identification variable mapping to a
* database table followed by the column name
*/
private void resolveColumn(AbstractPathExpression expression) {
String path = expression.getPath(1);
localExpression = localExpression.getField(path);
}
/**
* Attempts to resolve the path expression as a fully qualified enum constant.
*
* @param expression The {@link AbstractPathExpression} that might represent an enum constant
* @return <code>true</code> if the path was a fully qualified enum constant; <code>false</code>
* if it's an actual path expression
*/
protected boolean resolveEnumConstant(AbstractPathExpression expression) {
String fullPath = expression.toParsedText();
Class<?> enumType = queryContext.getEnumType(fullPath);
if (enumType != null) {
// Make sure we keep track of the enum type
type[0] = enumType;
// Retrieve the enum constant
String path = expression.getPath(expression.pathSize() - 1);
Enum<?> enumConstant = retrieveEnumConstant(enumType, path);
// Create the Expression
localExpression = new ConstantExpression(enumConstant, new ExpressionBuilder());
return true;
}
return false;
}
private void resolvePath(AbstractPathExpression expression) {
for (int index = expression.hasVirtualIdentificationVariable() ? 0 : 1, count = length; index < count; index++) {
String path = expression.getPath(index);
DatabaseMapping mapping = descriptor.getObjectBuilder().getMappingForAttributeName(path);
boolean last = (index + 1 == count);
boolean collectionMapping = false;
// The path is a mapping
if (mapping != null) {
// Make sure we keep track of its type
if (type != null) {
type[0] = queryContext.calculateMappingType(mapping);
}
// This will tell us how to create the Expression
collectionMapping = mapping.isCollectionMapping();
// Retrieve the reference descriptor so we can continue traversing the path
if (!last) {
descriptor = mapping.getReferenceDescriptor();
}
// Flag that needs to be modified for a special case
else if (checkMappingType) {
nullAllowed = mapping.isForeignReferenceMapping();
}
}
// No mapping is defined for the single path, check for a query key
else {
QueryKey queryKey = descriptor.getQueryKeyNamed(path);
if (queryKey != null) {
// Make sure we keep track of its type
if (type != null) {
type[0] = queryContext.calculateQueryKeyType(queryKey);
}
// This will tell us how to create the Expression
collectionMapping = queryKey.isCollectionQueryKey();
// Retrieve the reference descriptor so we can continue traversing the path
if (!last && queryKey.isForeignReferenceQueryKey()) {
ForeignReferenceQueryKey referenceQueryKey = (ForeignReferenceQueryKey) queryKey;
descriptor = queryContext.getDescriptor(referenceQueryKey.getReferenceClass());
}
}
// Nothing was found
else {
break;
}
}
// Now create the Expression
if (collectionMapping) {
if (last && nullAllowed) {
localExpression = localExpression.anyOfAllowingNone(path);
}
else {
localExpression = localExpression.anyOf(path);
}
}
else {
if (last && nullAllowed) {
localExpression = localExpression.getAllowingNull(path);
}
else {
localExpression = localExpression.get(path);
}
}
}
}
private void resolveVirtualPath(AbstractPathExpression expression) {
String path = expression.getPath(1);
localExpression = localExpression.get(path);
}
/**
* Retrieves the actual {@link Enum} constant with the given name.
*
* @param type The {@link Enum} class used to retrieve the given name
* @param name The name of the constant to retrieve
* @return The {@link Enum} constant
*/
private Enum<?> retrieveEnumConstant(Class<?> type, String name) {
for (Enum<?> enumConstant : (Enum<?>[]) type.getEnumConstants()) {
if (name.equals(enumConstant.name())) {
return enumConstant;
}
}
return null;
}
@Override
public void visit(CollectionValuedPathExpression expression) {
visitPathExpression(expression);
}
@Override
public void visit(EntryExpression expression) {
IdentificationVariable identificationVariable = (IdentificationVariable) expression.getExpression();
String variableName = identificationVariable.getVariableName();
// Retrieve the Expression for the identification variable
declaration = queryContext.findDeclaration(variableName);
declaration.getBaseExpression().accept(ExpressionBuilderVisitor.this);
localExpression = queryExpression;
// Create the Map.Entry expression
MapEntryExpression entryExpression = new MapEntryExpression(localExpression);
entryExpression.returnMapEntry();
localExpression = entryExpression;
}
@Override
public void visit(IdentificationVariable expression) {
expression.accept(ExpressionBuilderVisitor.this);
localExpression = queryExpression;
// It is possible the Expression is null, it happens when the path expression is an enum
// constant. If so, then no need to retrieve the descriptor
if (localExpression != null) {
declaration = queryContext.findDeclaration(expression.getVariableName());
descriptor = declaration.getDescriptor();
}
}
@Override
public void visit(KeyExpression expression) {
IdentificationVariable identificationVariable = (IdentificationVariable) expression.getExpression();
// Create the Expression for the identification variable
identificationVariable.accept(ExpressionBuilderVisitor.this);
localExpression = new MapEntryExpression(queryExpression);
// Retrieve the mapping's key mapping's descriptor
descriptor = queryContext.resolveDescriptor(expression);
}
@Override
public void visit(StateFieldPathExpression expression) {
visitPathExpression(expression);
}
@Override
public void visit(TreatExpression expression) {
expression.accept(ExpressionBuilderVisitor.this);
localExpression = queryExpression;
// Retrieve the mapping's key mapping's descriptor
descriptor = queryContext.resolveDescriptor(expression);
}
@Override
public void visit(ValueExpression expression) {
IdentificationVariable identificationVariable = (IdentificationVariable) expression.getExpression();
// Create the Expression for the identification variable
identificationVariable.accept(ExpressionBuilderVisitor.this);
localExpression = queryExpression;
// Retrieve the mapping's reference descriptor
declaration = queryContext.findDeclaration(identificationVariable.getVariableName());
descriptor = declaration.getDescriptor();
}
private void visitPathExpression(AbstractPathExpression expression) {
// First resolve the identification variable
expression.getIdentificationVariable().accept(this);
// The path expression is composed with an identification
// variable that is mapped to a subquery as the "root" object
if ((declaration != null) && (declaration.getType() == Type.SUBQUERY)) {
resolveVirtualPath(expression);
return;
}
// A null value would most likely mean it's coming from a
// state field path expression that represents an enum constant
if (localExpression == null) {
boolean result = resolveEnumConstant(expression);
if (result) {
return;
}
}
// The path expression is mapping to a database table
if ((declaration != null) && (declaration.getType() == Type.TABLE)) {
resolveColumn(expression);
return;
}
// Traverse the rest of the path expression
resolvePath(expression);
}
}
/**
* This visitor is responsible to create the {@link Expression Expressions} for the <b>WHEN</b>
* and <b>THEN</b> expressions.
*/
private class WhenClauseExpressionVisitor extends AbstractExpressionVisitor {
/**
* Keeps tracks of the type of each <code><b>WHEN</b></code> clauses.
*/
final List<Class<?>> types;
/**
* The map of <b>WHEN</b> expressions mapped to their associated <b>THEN</b> expression.
*/
Map<Expression, Expression> whenClauses;
/**
* Creates a new <code>WhenClauseExpressionVisitor</code>.
*/
WhenClauseExpressionVisitor() {
super();
types = new LinkedList<>();
whenClauses = new LinkedHashMap<>();
}
/**
* Disposes this visitor.
*/
void dispose() {
types.clear();
whenClauses.clear();
}
@Override
public void visit(CollectionExpression expression) {
expression.acceptChildren(this);
}
@Override
public void visit(WhenClause expression) {
// Create the WHEN expression
expression.getWhenExpression().accept(ExpressionBuilderVisitor.this);
Expression whenExpression = queryExpression;
// Create the THEN expression
expression.getThenExpression().accept(ExpressionBuilderVisitor.this);
Expression thenExpression = queryExpression;
types.add(type[0]);
whenClauses.put(whenExpression, thenExpression);
}
}
}