blob: 3e7b09909b34b2ad45a43959c57a74f3673389b5 [file] [log] [blame]
/*
* Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 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
//
// 09/02/2019-3.0 - Alexandre Jacob
// - 527415: Fix code when locale is tr, az or lt
package org.eclipse.persistence.jpa.jpql;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.eclipse.persistence.jpa.jpql.JPQLQueryDeclaration.Type;
import org.eclipse.persistence.jpa.jpql.parser.AbsExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractFromClause;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSingleEncapsulatedExpression;
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.ArithmeticExpression;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticFactor;
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.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.CompoundExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConcatExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConstructorExpression;
import org.eclipse.persistence.jpa.jpql.parser.CountFunction;
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.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.Expression;
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.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.JPQLGrammar;
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.RangeVariableDeclaration;
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.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.TreatExpression;
import org.eclipse.persistence.jpa.jpql.parser.TrimExpression;
import org.eclipse.persistence.jpa.jpql.parser.TypeExpression;
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 static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.*;
/**
* The base validator responsible to gather the problems found in a JPQL query by validating the
* content to make sure it is semantically valid, i.e. based on the information contained in the
* JPA application. The grammar is not validated by this visitor.
* <p>
* Provisional API: This interface is part of an interim API that is still under development and
* expected to change significantly before reaching stability. It is available at this early stage
* to solicit feedback from pioneering adopters on the understanding that any code that uses this
* API will almost certainly be broken (repeatedly) as the API evolves.
*
* @see AbstractGrammarValidator
*
* @version 2.5.1
* @since 2.4
* @author Pascal Filion
*/
@SuppressWarnings("nls")
public abstract class AbstractSemanticValidator extends AbstractValidator {
/**
* This visitor is responsible to retrieve the visited {@link Expression} if it is a
* {@link CollectionValuedPathExpression}.
*/
protected CollectionValuedPathExpressionVisitor collectionValuedPathExpressionVisitor;
/**
* This visitor is responsible to check if the entity type literal (parsed as an identification
* variable) is part of a comparison expression.
*/
private ComparingEntityTypeLiteralVisitor comparingEntityTypeLiteralVisitor;
/**
* This visitor visits the left and right expressions of a {@link ComparisonExpressionVisitor}
* and gather information that is used by {@link #validateComparisonExpression(ComparisonExpression)}.
*/
private ComparisonExpressionVisitor comparisonExpressionVisitor;
/**
* The given helper allows this validator to access the JPA artifacts without using Hermes SPI.
*/
protected final SemanticValidatorHelper helper;
/**
* This visitor makes sure the path expressions are properly validated.
*/
private InItemsVisitor inItemsVisitor;
/**
* This flag is used to register the {@link IdentificationVariable IdentificationVariables} that
* are used throughout the query (top-level query and subqueries), except the identification
* variables defining an abstract schema name or a collection-valued path expression.
*/
protected boolean registerIdentificationVariable;
/**
* This visitor is responsible to retrieve the visited {@link Expression} if it is a
* {@link StateFieldPathExpression}.
*/
protected StateFieldPathExpressionVisitor stateFieldPathExpressionVisitor;
/**
*
*/
private SubqueryFirstDeclarationVisitor subqueryFirstDeclarationVisitor;
/**
*
*/
private TopLevelFirstDeclarationVisitor topLevelFirstDeclarationVisitor;
/**
* The {@link IdentificationVariable IdentificationVariables} that are used throughout the query
* (top-level query and subqueries), except the identification variables defining an abstract
* schema name or a collection-valued path expression.
*/
protected List<IdentificationVariable> usedIdentificationVariables;
/**
* This finder is responsible to retrieve the virtual identification variable from the
* <b>UPDATE</b> range declaration since it is optional.
*/
protected BaseDeclarationIdentificationVariableFinder virtualIdentificationVariableFinder;
/**
* Creates a new <code>AbstractSemanticValidator</code>.
*
* @param helper The given helper allows this validator to access the JPA artifacts without using
* Hermes SPI
* @exception NullPointerException The given {@link SemanticValidatorHelper} cannot be <code>null</code>
*/
protected AbstractSemanticValidator(SemanticValidatorHelper helper) {
super();
Assert.isNotNull(helper, "The helper cannot be null");
this.helper = helper;
}
protected ComparingEntityTypeLiteralVisitor buildComparingEntityTypeLiteralVisitor() {
return new ComparingEntityTypeLiteralVisitor();
}
protected InItemsVisitor buildInItemsVisitor() {
return new InItemsVisitor(this);
}
protected SubqueryFirstDeclarationVisitor buildSubqueryFirstDeclarationVisitor() {
return new SubqueryFirstDeclarationVisitor();
}
protected TopLevelFirstDeclarationVisitor buildTopLevelFirstDeclarationVisitor() {
return new TopLevelFirstDeclarationVisitor();
}
@Override
public void dispose() {
super.dispose();
usedIdentificationVariables.clear();
}
/**
* Returns the {@link IdentificationVariable} that defines the identification variable for either
* a <code><b>DELETE</b></code> or an <code><b>UPDATE</b></code> query.
*
* @param expression The {@link AbstractSchemaName} that is being validated and that most likely
* representing an associated path expression and not an entity name
* @return The {@link IdentificationVariable} defining either the identification variable or the
* virtual identification variable for the <code><b>DELETE</b></code> or for the
* <code><b>UPDATE</b></code> query
*/
protected IdentificationVariable findVirtualIdentificationVariable(AbstractSchemaName expression) {
BaseDeclarationIdentificationVariableFinder visitor = getVirtualIdentificationVariableFinder();
try {
expression.accept(visitor);
return visitor.expression;
}
finally {
visitor.expression = null;
}
}
protected CollectionValuedPathExpression getCollectionValuedPathExpression(Expression expression) {
CollectionValuedPathExpressionVisitor visitor = getCollectionValuedPathExpressionVisitor();
try {
expression.accept(visitor);
return visitor.expression;
}
finally {
visitor.expression = null;
}
}
protected CollectionValuedPathExpressionVisitor getCollectionValuedPathExpressionVisitor() {
if (collectionValuedPathExpressionVisitor == null) {
collectionValuedPathExpressionVisitor = new CollectionValuedPathExpressionVisitor();
}
return collectionValuedPathExpressionVisitor;
}
protected ComparingEntityTypeLiteralVisitor getComparingEntityTypeLiteralVisitor() {
if (comparingEntityTypeLiteralVisitor == null) {
comparingEntityTypeLiteralVisitor = buildComparingEntityTypeLiteralVisitor();
}
return comparingEntityTypeLiteralVisitor;
}
protected ComparisonExpressionVisitor getComparisonExpressionVisitor() {
if (comparisonExpressionVisitor == null) {
comparisonExpressionVisitor = new ComparisonExpressionVisitor(this);
}
return comparisonExpressionVisitor;
}
@Override
protected JPQLGrammar getGrammar() {
return helper.getGrammar();
}
protected InItemsVisitor getInItemsVisitor() {
if (inItemsVisitor == null) {
inItemsVisitor = buildInItemsVisitor();
}
return inItemsVisitor;
}
protected StateFieldPathExpression getStateFieldPathExpression(Expression expression) {
StateFieldPathExpressionVisitor visitor = getStateFieldPathExpressionVisitor();
try {
expression.accept(visitor);
return visitor.expression;
}
finally {
visitor.expression = null;
}
}
protected StateFieldPathExpressionVisitor getStateFieldPathExpressionVisitor() {
if (stateFieldPathExpressionVisitor == null) {
stateFieldPathExpressionVisitor = new StateFieldPathExpressionVisitor();
}
return stateFieldPathExpressionVisitor;
}
/**
* Returns the visitor that can find the {@link IdentificationVariable} of the {@link
* RangeVariableDeclaration}. This should be used when the query is either a <code><b>DELETE</b></code>
* or <code><b>UPDATE</b></code> query.
*
* @return The visitor that can traverse the query and returns the {@link IdentificationVariable}
*/
protected BaseDeclarationIdentificationVariableFinder getVirtualIdentificationVariableFinder() {
if (virtualIdentificationVariableFinder == null) {
virtualIdentificationVariableFinder = new BaseDeclarationIdentificationVariableFinder();
}
return virtualIdentificationVariableFinder;
}
@Override
protected void initialize() {
super.initialize();
usedIdentificationVariables = new ArrayList<>();
registerIdentificationVariable = true;
}
/**
* Determines whether the given identification variable is used in a comparison expression:
* "expression = LargeProject".
*
* @param expression The {@link IdentificationVariable} used to determine its purpose
* @return <code>true</code> if the identification variable is used in a comparison expression;
* <code>false</code> otherwise
*/
protected boolean isComparingEntityTypeLiteral(IdentificationVariable expression) {
ComparingEntityTypeLiteralVisitor visitor = getComparingEntityTypeLiteralVisitor();
try {
visitor.expression = expression;
expression.accept(visitor);
return visitor.result;
}
finally {
visitor.result = false;
visitor.expression = null;
}
}
protected boolean isIdentificationVariableDeclaredAfter(String variableName,
int variableNameIndex,
int joinIndex,
List<? extends JPQLQueryDeclaration> declarations) {
for (int index = variableNameIndex, declarationCount = declarations.size(); index < declarationCount; index++) {
JPQLQueryDeclaration declaration = declarations.get(index);
// Ignore the current declaration since it's the same identification variable
if (index != variableNameIndex) {
// Check to make sure the Declaration is a known type
if (declaration.getType() != Type.UNKNOWN) {
String nextVariableName = declaration.getVariableName();
if (variableName.equalsIgnoreCase(nextVariableName)) {
return true;
}
}
// Some implementation could have a Declaration that is not a range, derived or a
// collection Declaration, it could represent a JOIN expression
else {
return false;
}
}
// Scan the JOIN expressions
if (declaration.hasJoins()) {
List<Join> joins = declaration.getJoins();
int endIndex = (index == variableNameIndex) ? joinIndex : joins.size();
for (int subIndex = joinIndex; subIndex < endIndex; subIndex++) {
Join join = joins.get(subIndex);
String joinVariableName = literal(
join.getIdentificationVariable(),
LiteralType.IDENTIFICATION_VARIABLE
);
if (variableName.equalsIgnoreCase(joinVariableName)) {
return true;
}
}
}
}
// The identification variable was not found after the declaration being validated, that means
// it was defined before or it was not defined (which is validated by another rule)
return false;
}
/**
* Determines whether an identification variable can be used in a comparison expression when the
* operator is either {@literal '<', '<=', '>', '>='}.
*
* @param expression The {@link IdentificationVariable} that is mapped to either an entity, a
* singled-object value field, a collection-valued object field
* @return <code>true</code> if it can be used in a ordering comparison expression; <code>false</code>
* if it can't
*/
protected boolean isIdentificationVariableValidInComparison(IdentificationVariable expression) {
return helper.isIdentificationVariableValidInComparison(expression);
}
/**
* Determines whether the given {@link ComparisonExpression} compares two expression using one of
* the following operators: {@literal '<', '<=', '>', '>='}.
*
* @param expression The {@link ComparisonExpression} to check what type of operator that is used
* @return <code>true</code> if the operator is used to check for order; <code>false</code> if it
* is not
*/
protected boolean isOrderComparison(ComparisonExpression expression) {
String operator = expression.getComparisonOperator();
return operator == Expression.GREATER_THAN ||
operator == Expression.GREATER_THAN_OR_EQUAL ||
operator == Expression.LOWER_THAN ||
operator == Expression.LOWER_THAN_OR_EQUAL;
}
/**
* Determines whether the expression at the given index is valid or not.
*
* @param result The integer value containing the bit used to determine the state of an expression
* at a given position
* @param index The index that is used to determine the state of the expression
* @return <code>true</code> if the expression is valid at the given index
*/
protected final boolean isValid(int result, int index) {
return (result & (1 << index)) == 0;
}
/**
* Returns the type of path expression that is allowed in the <code><b>SELECT</b></code> clause.
*
* @return The type of path expressions allowed. The spec defines it as basic or object mapping
* only, i.e. collection-valued path expression is not allowed
*/
protected abstract PathType selectClausePathExpressionPathType();
protected FirstDeclarationVisitor subqueryFirstDeclarationVisitor() {
if (subqueryFirstDeclarationVisitor == null) {
subqueryFirstDeclarationVisitor = buildSubqueryFirstDeclarationVisitor();
}
return subqueryFirstDeclarationVisitor;
}
protected FirstDeclarationVisitor topLevelFirstDeclarationVisitor() {
if (topLevelFirstDeclarationVisitor == null) {
topLevelFirstDeclarationVisitor = buildTopLevelFirstDeclarationVisitor();
}
return topLevelFirstDeclarationVisitor;
}
/**
* Updates the validation status of an expression at a specified position. The value is stored
* in an integer value.
*
* @param result The integer value that is used to store the validation status of an expression
* at the given position
* @param index The position to store the validation status
* @param valid The new validation status to store
* @return The updated integer value
*/
protected final int updateStatus(int result, int index, boolean valid) {
return valid ? (result & (0 << index)) : (result | (1 << index));
}
/**
* Validates the encapsulated expression of the given <code><b>ABS</b></code> expression. The
* test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link AbsExpression} to validate by validating its encapsulated
* expression
* @return <code>false</code> if the encapsulated expression was validated and is invalid;
* <code>true</code> otherwise
*/
protected boolean validateAbsExpression(AbsExpression expression) {
return validateFunctionPathExpression(expression);
}
/**
* Validates the given <code><b>FROM</b></code> clause. This will validate the order of
* identification variable declarations.
*
* @param expression The {@link AbstractFromClause} to validate
*/
protected void validateAbstractFromClause(AbstractFromClause expression,
FirstDeclarationVisitor visitor) {
// The identification variable declarations are evaluated from left to right in
// the FROM clause, and an identification variable declaration can use the result
// of a preceding identification variable declaration of the query string
List<? extends JPQLQueryDeclaration> declarations = helper.getDeclarations();
for (int index = 0, count = declarations.size(); index < count; index++) {
JPQLQueryDeclaration declaration = declarations.get(index);
// Make sure the first declaration is valid
if (index == 0) {
validateFirstDeclaration(expression, declaration, visitor);
}
else {
Expression declarationExpression = declaration.getDeclarationExpression();
// A JOIN expression does not have a declaration and it should not be traversed here
if (declarationExpression != null) {
declarationExpression.accept(this);
}
}
// Check the JOIN expressions in the identification variable declaration
if (declaration.hasJoins()) {
validateJoinsIdentificationVariable(expression, declarations, declaration, index);
}
// Check the collection member declaration or derived path expression
else {
Type type = declaration.getType();
if (type == Type.DERIVED ||
type == Type.COLLECTION) {
// Retrieve the identification variable from the path expression
String variableName = literal(
declaration.getBaseExpression(),
LiteralType.PATH_EXPRESSION_IDENTIFICATION_VARIABLE
);
if (ExpressionTools.stringIsNotEmpty(variableName) &&
isIdentificationVariableDeclaredAfter(variableName, index, -1, declarations)) {
int startPosition = position(declaration.getDeclarationExpression()) - variableName.length();
int endPosition = startPosition + variableName.length();
addProblem(
expression,
startPosition,
endPosition,
AbstractFromClause_WrongOrderOfIdentificationVariableDeclaration,
variableName
);
}
}
}
}
}
/**
* Validates the given {@link AbstractSchemaName}. The tests to perform are:
* <ul>
* <li>Check to see the actual entity associated with the entity name does exist.</li>
* <li>If the abstract schema name is actually a path expression (which can be defined in a
* subquery but is always parsed as an abstract schema name), then make sure the path
* expression is resolving to a relationship mapping.</li>
* </ul>
*
* @param expression The {@link AbstractSchemaName} to validate
* @return <code>true</code> if the entity name was resolved; <code>false</code> otherwise
*/
protected boolean validateAbstractSchemaName(AbstractSchemaName expression) {
String abstractSchemaName = expression.getText();
Object managedType = helper.getEntityNamed(abstractSchemaName);
boolean valid = true;
if (managedType == null) {
// If a subquery is defined in a WHERE clause of an update query,
// then check for a path expression
if (isWithinSubquery(expression)) {
// Find the identification variable from the UPDATE range declaration
IdentificationVariable identificationVariable = findVirtualIdentificationVariable(expression);
String variableName = (identificationVariable != null) ? identificationVariable.getText() : null;
if (ExpressionTools.stringIsNotEmpty(variableName)) {
Object mapping = helper.resolveMapping(variableName, abstractSchemaName);
Object type = helper.getMappingType(mapping);
// Does not resolve to a valid path
if (!helper.isTypeResolvable(type)) {
addProblem(expression, StateFieldPathExpression_NotResolvable, abstractSchemaName);
valid = false;
}
// Not a relationship mapping
else if (!helper.isRelationshipMapping(mapping)) {
addProblem(expression, PathExpression_NotRelationshipMapping, abstractSchemaName);
valid = false;
}
}
else {
addProblem(expression, AbstractSchemaName_Invalid, abstractSchemaName);
valid = false;
}
}
// The managed type does not exist
else {
addProblem(expression, AbstractSchemaName_Invalid, abstractSchemaName);
valid = false;
}
}
return valid;
}
/**
* Validates the encapsulated expression of the given addition expression. The test to perform is:
* <ul>
* <li>If the left or right expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the left or right expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be updated.</li>
* </ul>
*
* @param expression The {@link AdditionExpression} to validate by validating its encapsulated
* expression
* @return A number indicating the validation result. {@link #isValid(int, int)} can be used to
* determine the validation status of an expression based on its position
*/
protected int validateAdditionExpression(AdditionExpression expression) {
return validateArithmeticExpression(
expression,
AdditionExpression_LeftExpression_WrongType,
AdditionExpression_RightExpression_WrongType
);
}
/**
* Validates the given {@link AllOrAnyExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link AllOrAnyExpression} to validate
*/
protected void validateAllOrAnyExpression(AllOrAnyExpression expression) {
super.visit(expression);
}
/**
* Validates the given {@link AndExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link AndExpression} to validate
*/
protected void validateAndExpression(AndExpression expression) {
super.visit(expression);
}
/**
* Validates the type of the left and right expressions defined by the given {@link ArithmeticExpression}.
* The test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link ArithmeticExpression} to validate
* @param leftExpressionWrongTypeMessageKey The key used to describe the left expression does not
* have a valid type
* @param rightExpressionWrongTypeMessageKey The key used to describe the right expression does
* not have a valid type
* @return A number indicating the validation result. {@link #isValid(int, int)} can be used to
* determine the validation status of an expression based on its position
*/
protected int validateArithmeticExpression(ArithmeticExpression expression,
String leftExpressionWrongTypeMessageKey,
String rightExpressionWrongTypeMessageKey) {
return validateFunctionPathExpression(expression, PathType.BASIC_FIELD_ONLY);
}
/**
* Validates the arithmetic factor expression. The test to perform is:
* <ul>
* <li>If the arithmetic factor is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* </ul>
*
* @param expression The {@link ArithmeticFactor} to validate
* @return <code>false</code> if the arithmetic factor expression was validated and is invalid;
* <code>true</code> otherwise
*/
protected boolean validateArithmeticExpression(ArithmeticFactor expression) {
boolean valid = true;
if (expression.hasExpression()) {
Expression factor = expression.getExpression();
// Special case for state field path expression, association field is not allowed
StateFieldPathExpression pathExpression = getStateFieldPathExpression(factor);
if (pathExpression != null) {
valid = validateStateFieldPathExpression(pathExpression, PathType.BASIC_FIELD_ONLY);
}
else {
factor.accept(this);
}
}
return valid;
}
/**
* Validates the encapsulated expression of the given <code><b>AVG</b></code> expression. The
* test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link AvgFunction} to validate by validating its encapsulated expression
* @return <code>false</code> if the encapsulated expression was validated and is invalid;
* <code>true</code> otherwise
*/
protected boolean validateAvgFunction(AvgFunction expression) {
return validateFunctionPathExpression(expression);
}
/**
* Validates the given {@link BetweenExpression}. The test to perform is:
* <ul>
* <li>If the "first" expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* </ul>
*
* @param expression The {@link BetweenExpression} to validate
*/
protected int validateBetweenExpression(BetweenExpression expression) {
int result = 0;
// Validate the "first" expression
if (expression.hasExpression()) {
Expression firstExpression = expression.getExpression();
// Special case for state field path expression, association field is not allowed
StateFieldPathExpression pathExpression = getStateFieldPathExpression(firstExpression);
if (pathExpression != null) {
boolean valid = validateStateFieldPathExpression(pathExpression, PathType.BASIC_FIELD_ONLY);
updateStatus(result, 0, valid);
}
else {
firstExpression.accept(this);
}
}
// Validate the lower bound expression
expression.getLowerBoundExpression().accept(this);
// Validate the upper bound expression
expression.getUpperBoundExpression().accept(this);
return result;
}
/**
* Validates the given {@link CaseExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link CaseExpression} to validate
*/
protected void validateCaseExpression(CaseExpression expression) {
super.visit(expression);
}
/**
* Validates the given {@link CoalesceExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link CoalesceExpression} to validate
*/
protected void validateCoalesceExpression(CoalesceExpression expression) {
super.visit(expression);
}
/**
* Validates the given {@link CollectionMemberDeclaration}.
*
* @param expression The {@link CollectionMemberDeclaration} to validate
*/
protected void validateCollectionMemberDeclaration(CollectionMemberDeclaration expression) {
validateCollectionValuedPathExpression(expression.getCollectionValuedPathExpression(), true);
try {
registerIdentificationVariable = false;
expression.getIdentificationVariable().accept(this);
}
finally {
registerIdentificationVariable = true;
}
}
/**
* Validates the given {@link CollectionMemberExpression}. Only the collection-valued path
* expression is validated.
*
* @param expression The {@link CollectionMemberExpression} to validate
* @return A number indicating the validation result. {@link #isValid(int, int)} can be used to
* determine the validation status of an expression based on its position
*/
protected int validateCollectionMemberExpression(CollectionMemberExpression expression) {
int result = 0;
// Validate the entity expression
if (expression.hasEntityExpression()) {
Expression entityExpression = expression.getEntityExpression();
// Special case for state field path expression, association field is allowed
StateFieldPathExpression pathExpression = getStateFieldPathExpression(entityExpression);
if (pathExpression != null) {
boolean valid = validateStateFieldPathExpression(pathExpression, PathType.ASSOCIATION_FIELD_ONLY);
updateStatus(result, 0, valid);
}
else {
entityExpression.accept(this);
}
}
// Validate the collection-valued path expression
boolean valid = validateCollectionValuedPathExpression(expression.getCollectionValuedPathExpression(), true);
updateStatus(result, 1, valid);
return result;
}
/**
* Validates the given {@link Expression} and makes sure it's a valid collection value path expression.
*
* @param expression The {@link Expression} to validate
* @param collectionTypeOnly <code>true</code> to make sure the path expression resolves to a
* collection mapping only; <code>false</code> if it can simply resolves to a relationship mapping
*/
protected boolean validateCollectionValuedPathExpression(Expression expression,
boolean collectionTypeOnly) {
boolean valid = true;
// The path expression resolves to a collection-valued path expression
CollectionValuedPathExpression collectionValuedPathExpression = getCollectionValuedPathExpression(expression);
if (collectionValuedPathExpression != null &&
collectionValuedPathExpression.hasIdentificationVariable() &&
!collectionValuedPathExpression.endsWithDot()) {
// A collection_valued_field is designated by the name of an association field in a
// one-to-many or a many-to-many relationship or by the name of an element collection field
Object mapping = helper.resolveMapping(expression);
Object type = helper.getMappingType(mapping);
// Does not resolve to a valid path
if (!helper.isTypeResolvable(type) || (mapping == null)) {
int startPosition = position(expression);
int endPosition = startPosition + length(expression);
addProblem(
expression,
startPosition,
endPosition,
CollectionValuedPathExpression_NotResolvable,
expression.toParsedText()
);
valid = false;
}
else if (collectionTypeOnly && !helper.isCollectionMapping(mapping) ||
!collectionTypeOnly && !helper.isRelationshipMapping(mapping)) {
int startPosition = position(expression);
int endPosition = startPosition + length(expression);
addProblem(
expression,
startPosition,
endPosition,
CollectionValuedPathExpression_NotCollectionType,
expression.toParsedText()
);
valid = false;
}
}
return valid;
}
/**
* Validates the given {@link Expression} and makes sure it's a valid collection value path expression.
*
* join_collection_valued_path_expression::=
* identification_variable.{single_valued_embeddable_object_field.}*collection_valued_field
* join_single_valued_path_expression::=
* identification_variable.{single_valued_embeddable_object_field.}*single_valued_object_field
*
* @param expression The {@link Expression} to validate
* @param collectionTypeOnly <code>true</code> to make sure the path expression resolves to a
* collection mapping only; <code>false</code> if it can simply resolves to a relationship mapping
*/
protected boolean validateJoinCollectionValuedPathExpression(Expression expression,
boolean collectionTypeOnly) {
boolean valid = true;
// The path expression resolves to a collection-valued path expression
CollectionValuedPathExpression collectionValuedPathExpression = getCollectionValuedPathExpression(expression);
if (collectionValuedPathExpression != null &&
collectionValuedPathExpression.hasIdentificationVariable() &&
!collectionValuedPathExpression.endsWithDot()) {
// A collection_valued_field is designated by the name of an association field in a
// one-to-many or a many-to-many relationship or by the name of an element collection field
// A single_valued_object_field is designated by the name of an association field in a one-to-one or
// many-to-one relationship or a field of embeddable class type
Object mapping = helper.resolveMapping(expression);
Object type = helper.getMappingType(mapping);
// Does not resolve to a valid path
if (!helper.isTypeResolvable(type) || (mapping == null)) {
int startPosition = position(expression);
int endPosition = startPosition + length(expression);
addProblem(
expression,
startPosition,
endPosition,
CollectionValuedPathExpression_NotResolvable,
expression.toParsedText()
);
valid = false;
}
else if (!helper.isCollectionMapping(mapping) &&
!helper.isRelationshipMapping(mapping) &&
!helper.isEmbeddableMapping(mapping)) {
int startPosition = position(expression);
int endPosition = startPosition + length(expression);
addProblem(
expression,
startPosition,
endPosition,
CollectionValuedPathExpression_NotCollectionType,
expression.toParsedText()
);
valid = false;
}
}
return valid;
}
/**
* Validates the left and right expressions of the given {@link ComparisonExpression}. The tests
* to perform are:
* <ul>
* <li>If the comparison operator is either '=' or {@literal '<>'}. The expressions can only be
* <ul>
* <li>Two identification variables;</li>
* <li>Two path expressions resolving to an association field;</li>
* <li>One can be a path expression resolving to a basic field and the other one has to
* resolve to a basic value.</li>
* </ul>
* </li>
* <li>If the comparison operator is either {@literal '<', '<=', '>=', '>'}. The expressions cannot be
* <ul>
* <li>Two identification variables;</li>
* <li>Two path expressions resolving to an association field;</li>
* </ul>
* </li>
* </ul>
*
* @param expression The {@link ConcatExpression} to validate by validating its
* left and right expressions
* @return The status of the comparison between the left and right expression: <code>true</code>
* if the two expressions pass the rules defined by this method; <code>false</code> otherwise
*/
protected boolean validateComparisonExpression(ComparisonExpression expression) {
Expression leftExpression = expression.getLeftExpression();
Expression rightExpression = expression.getRightExpression();
boolean valid = true;
// First determine what is being compared and validate them as well
ComparisonExpressionVisitor validator = getComparisonExpressionVisitor();
try {
// Visit the left expression and gather its information
validator.validatingLeftExpression = true;
leftExpression.accept(validator);
// Visit the right expression and gather its information
validator.validatingLeftExpression = false;
rightExpression.accept(validator);
// '<', '<=', '>=', '>'
if (isOrderComparison(expression)) {
// The left expression cannot be an identification variable
if (validator.leftIdentificationVariable &&
validator.leftIdentificationVariableValid) {
IdentificationVariable variable = (IdentificationVariable) leftExpression;
// There is a specific EclipseLink case where it is valid to use an
// identification variable, which is when the identification variable
// maps to a direct collection mapping
if (!isIdentificationVariableValidInComparison(variable)) {
addProblem(
leftExpression,
ComparisonExpression_IdentificationVariable,
leftExpression.toActualText(),
expression.getComparisonOperator()
);
valid = false;
}
}
// The left expression is a path expression
else if (validator.leftStateFieldPathExpression &&
validator.leftStateFieldPathExpressionValid) {
Object mapping = helper.resolveMapping(leftExpression);
// The path expression cannot be a non-basic mapping
if ((mapping != null) && !helper.isPropertyMapping(mapping)) {
addProblem(
leftExpression,
ComparisonExpression_AssociationField,
leftExpression.toActualText(),
expression.getComparisonOperator()
);
valid = false;
}
}
// The right expression cannot be an identification variable
if (validator.rightIdentificationVariable &&
validator.rightIdentificationVariableValid) {
IdentificationVariable variable = (IdentificationVariable) rightExpression;
// There is a specific EclipseLink case where it is valid to use an
// identification variable, which is when the identification variable
// maps to a direct collection mapping
if (!isIdentificationVariableValidInComparison(variable)) {
addProblem(
rightExpression,
ComparisonExpression_IdentificationVariable,
rightExpression.toActualText(),
expression.getComparisonOperator()
);
valid = false;
}
}
// The right expression is a path expression
else if (validator.rightStateFieldPathExpression &&
validator.rightStateFieldPathExpressionValid) {
Object mapping = helper.resolveMapping(rightExpression);
// The path expression cannot be a non-basic mapping
if ((mapping != null) && !helper.isPropertyMapping(mapping)) {
addProblem(
rightExpression,
ComparisonExpression_AssociationField,
rightExpression.toActualText(),
expression.getComparisonOperator()
);
valid = false;
}
}
}
// '=', '<>'
else {
// The left expression is an identification variable
// The right expression is a path expression
if (validator.leftIdentificationVariable &&
validator.leftIdentificationVariableValid &&
validator.rightStateFieldPathExpression &&
validator.rightStateFieldPathExpressionValid) {
Object mapping = helper.resolveMapping(rightExpression);
IdentificationVariable variable = (IdentificationVariable) leftExpression;
// The path expression can only be a non-basic mapping.
// There is a specific EclipseLink case where it is valid to use an
// identification variable, which is when the identification variable
// maps to a direct collection mapping
if ((mapping != null) && helper.isPropertyMapping(mapping) &&
!isIdentificationVariableValidInComparison(variable)) {
addProblem(
rightExpression,
ComparisonExpression_BasicField,
rightExpression.toActualText(),
expression.getComparisonOperator()
);
valid = false;
}
}
// The left expression is a path expression
// The right expression is an identification variable
else if (validator.rightIdentificationVariable &&
validator.rightIdentificationVariableValid &&
validator.leftStateFieldPathExpression &&
validator.leftStateFieldPathExpressionValid) {
Object mapping = helper.resolveMapping(leftExpression);
IdentificationVariable variable = (IdentificationVariable) rightExpression;
// The path expression can only be a non-basic mapping.
// There is a specific EclipseLink case where it is valid to use an
// identification variable, which is when the identification variable
// maps to a direct collection mapping
if ((mapping != null) && helper.isPropertyMapping(mapping) &&
!isIdentificationVariableValidInComparison(variable)) {
addProblem(
leftExpression,
ComparisonExpression_BasicField,
leftExpression.toActualText(),
expression.getComparisonOperator()
);
valid = false;
}
}
}
return valid;
}
finally {
validator.dispose();
}
}
/**
* Validates the encapsulated expression of the given <code><b>CONCAT</b></code> expression.
* The tests to perform are:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a
* basic mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected
* to that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link ConcatExpression} to validate by validating its encapsulated expression
* @return <code>false</code> if the first encapsulated expression was validated and is invalid;
* <code>true</code> otherwise
*/
protected boolean validateConcatExpression(ConcatExpression expression) {
return validateFunctionPathExpression(expression);
}
/**
* Validates the given {@link ConstructorExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link ConstructorExpression} to validate
*/
protected void validateConstructorExpression(ConstructorExpression expression) {
super.visit(expression);
}
/**
* Validates the given {@link CountFunction}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link CountFunction} to validate
*/
protected void validateCountFunction(CountFunction expression) {
if (expression.hasExpression()) {
Expression leftExpression = expression.getExpression();
StateFieldPathExpression pathExpression = getStateFieldPathExpression(leftExpression);
if (pathExpression != null) {
validateStateFieldPathExpression(pathExpression, validPathExpressionTypeForCountFunction());
}
else {
leftExpression.accept(this);
}
}
}
/**
* Validates the given {@link DateTime}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link DateTime} to validate
*/
protected void validateDateTime(DateTime expression) {
super.visit(expression);
}
/**
* Validates the given {@link DeleteClause}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link DeleteClause} to validate
*/
protected void validateDeleteClause(DeleteClause expression) {
super.visit(expression);
}
/**
* Validates the given {@link DeleteStatement}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link DeleteStatement} to validate
*/
protected void validateDeleteStatement(DeleteStatement expression) {
super.visit(expression);
}
/**
* Validates the encapsulated expression of the given division expression. The test to perform is:
* <ul>
* <li>If the left or right expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the left or right expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be updated.</li>
* </ul>
*
* @param expression The {@link DivisionExpression} to validate by validating its encapsulated
* expression
* @return A number indicating the validation result. {@link #isValid(int, int)} can be used to
* determine the validation status of an expression based on its position
*/
protected int validateDivisionExpression(DivisionExpression expression) {
return validateArithmeticExpression(
expression,
DivisionExpression_LeftExpression_WrongType,
DivisionExpression_RightExpression_WrongType
);
}
protected boolean validateEntityTypeLiteral(EntityTypeLiteral expression) {
String entityTypeName = expression.getEntityTypeName();
boolean valid = true;
if (ExpressionTools.stringIsNotEmpty(entityTypeName)) {
Object entity = helper.getEntityNamed(entityTypeName);
if (entity == null) {
int startIndex = position(expression);
int endIndex = startIndex + entityTypeName.length();
addProblem(expression, startIndex, endIndex, EntityTypeLiteral_NotResolvable, entityTypeName);
valid = false;
}
}
return valid;
}
/**
* Validates the given {@link EntryExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link EntryExpression} to validate
*/
protected void validateEntryExpression(EntryExpression expression) {
super.visit(expression);
}
/**
* Validates the given {@link ExistsExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link ExistsExpression} to validate
*/
protected void validateExistsExpression(ExistsExpression expression) {
super.visit(expression);
}
protected void validateFirstDeclaration(AbstractFromClause expression,
JPQLQueryDeclaration declaration,
FirstDeclarationVisitor visitor) {
Expression declarationExpression = declaration.getDeclarationExpression();
Expression baseExpression = declaration.getBaseExpression();
try {
baseExpression.accept(visitor);
if (!visitor.valid) {
int startPosition = position(declarationExpression);
int endPosition = startPosition + length(declarationExpression);
addProblem(
expression,
startPosition,
endPosition,
AbstractFromClause_InvalidFirstIdentificationVariableDeclaration,
baseExpression.toActualText()
);
}
else {
declarationExpression.accept(this);
}
}
finally {
visitor.valid = false;
}
}
/**
* Validates the given {@link FromClause}.
*
* @param expression The {@link FromClause} to validate
*/
protected void validateFromClause(FromClause expression) {
validateAbstractFromClause(expression, topLevelFirstDeclarationVisitor());
}
/**
* Validates the given {@link FunctionExpression}.
*
* @param expression The {@link FunctionExpression} to validate
*/
protected void validateFunctionExpression(FunctionExpression expression) {
}
/**
* Validates the given {@link AbstractSingleEncapsulatedExpression}'s encapsulated expression if
* it is a state field path expression and makes sure it is mapping to a basic mapping. That
* means relationship field mapping is not allowed.
*
* @param expression The {@link AbstractSingleEncapsulatedExpression} to validate its encapsulated
* expression if it's a state field path expression, otherwise does nothing
* @return <code>false</code> if the encapsulated expression was validated and is invalid;
* <code>true</code> otherwise
*/
protected boolean validateFunctionPathExpression(AbstractSingleEncapsulatedExpression expression) {
boolean valid = true;
if (expression.hasEncapsulatedExpression()) {
Expression encapsulatedExpression = expression.getExpression();
// Special case for state field path expression, association field is not allowed
StateFieldPathExpression pathExpression = getStateFieldPathExpression(encapsulatedExpression);
if (pathExpression != null) {
valid = validateStateFieldPathExpression(pathExpression, PathType.BASIC_FIELD_ONLY);
}
else {
encapsulatedExpression.accept(this);
}
}
return valid;
}
/**
* Validates the left and right expressions of the given compound expression. The test to perform is:
* <ul>
* <li>If the left or the right expression is a path expression, validation makes sure it is a
* basic mapping, an association field is not allowed.</li>
* </ul>
*
* @param expression The {@link CompoundExpression} to validate by validating its left and right
* expressions
* @param pathType The type of field that is allowed
* @return A number indicating the validation result. {@link #isValid(int, int)} can be used to
* determine the validation status of an expression based on its position
*/
protected int validateFunctionPathExpression(CompoundExpression expression, PathType pathType) {
int result = 0;
// Left expression
if (expression.hasLeftExpression()) {
Expression leftExpression = expression.getLeftExpression();
StateFieldPathExpression pathExpression = getStateFieldPathExpression(leftExpression);
if (pathExpression != null) {
boolean valid = validateStateFieldPathExpression(pathExpression, pathType);
updateStatus(result, 0, valid);
}
else {
leftExpression.accept(this);
}
}
// Right expression
if (expression.hasRightExpression()) {
Expression rightExpression = expression.getRightExpression();
StateFieldPathExpression pathExpression = getStateFieldPathExpression(rightExpression);
if (pathExpression != null) {
boolean valid = validateStateFieldPathExpression(pathExpression, pathType);
updateStatus(result, 1, valid);
}
else {
rightExpression.accept(this);
}
}
return result;
}
/**
* Validates the given {@link GroupByClause}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link GroupByClause} to validate
*/
protected void validateGroupByClause(GroupByClause expression) {
super.visit(expression);
}
/**
* Validates the given {@link HavingClause}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link HavingClause} to validate
*/
protected void validateHavingClause(HavingClause expression) {
super.visit(expression);
}
/**
* Validates the given {@link IdentificationVariable}. The test to perform are:
* <ul>
* <li>If the identification variable resolves to an entity type literal, then no validation
* is performed.</li>
* <li></li>
* </ul>
*
* @param expression The identification variable to be validated
* @return <code>true</code> if the given identification variable is valid; <code>false</code>
* otherwise
*/
protected boolean validateIdentificationVariable(IdentificationVariable expression) {
boolean valid = true;
// Only a non-virtual identification variable is validated
if (!expression.isVirtual()) {
String variable = expression.getText();
boolean continueValidating = true;
// A entity literal type is parsed as an identification variable, check for that case
if (isComparingEntityTypeLiteral(expression)) {
// The identification variable (or entity type literal) does not
// correspond to an entity name, then continue validation
Object entity = helper.getEntityNamed(variable);
continueValidating = (entity == null);
}
// Validate a real identification variable
if (continueValidating) {
if (registerIdentificationVariable) {
usedIdentificationVariables.add(expression);
}
valid = validateIdentificationVariable(expression, variable);
}
}
// The identification variable actually represents a state field path expression that has
// a virtual identification, validate that state field path expression instead
else {
StateFieldPathExpression pathExpression = expression.getStateFieldPathExpression();
if (pathExpression != null) {
pathExpression.accept(this);
}
}
return valid;
}
/**
* Validates the given identification variable. The default behavior is to not validate it.
*
* @param expression The {@link IdentificationVariable} that is being visited
* @param variable The actual identification variable, which is never an empty string
* @return <code>true</code> if the given identification variable is valid; <code>false</code>
* otherwise
*/
protected boolean validateIdentificationVariable(IdentificationVariable expression, String variable) {
return true;
}
/**
* Validates the given {@link InExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link InExpression} to validate
*/
protected void validateIdentificationVariableDeclaration(IdentificationVariableDeclaration expression) {
super.visit(expression);
}
/**
* Validates the identification variables:
* <ul>
* <li>Assures those used throughout the query have been defined in the <code><b>FROM</b></code>
* clause in the current subquery or in a superquery.</li>
* <li>They have been defined only once.</li>
* </ul>
*/
protected void validateIdentificationVariables() {
// Collect the identification variables from the Declarations
Map<String, List<IdentificationVariable>> identificationVariables = new HashMap<>();
helper.collectLocalDeclarationIdentificationVariables(identificationVariables);
// Check for duplicate identification variables
for (Map.Entry<String, List<IdentificationVariable>> entry : identificationVariables.entrySet()) {
List<IdentificationVariable> variables = entry.getValue();
// More than one identification variable was used in a declaration
if (variables.size() > 1) {
for (IdentificationVariable variable : variables) {
addProblem(variable, IdentificationVariable_Invalid_Duplicate, variable.getText());
}
}
}
// Now collect the identification variables from the parent queries
identificationVariables.clear();
helper.collectAllDeclarationIdentificationVariables(identificationVariables);
// Check for undeclared identification variables
for (IdentificationVariable identificationVariable : usedIdentificationVariables) {
String variableName = identificationVariable.getText();
if (ExpressionTools.stringIsNotEmpty(variableName) &&
!identificationVariables.containsKey(variableName.toUpperCase(Locale.ROOT))) {
addProblem(identificationVariable, IdentificationVariable_Invalid_NotDeclared, variableName);
}
}
}
/**
* Validates the given {@link IndexExpression}. It validates the identification variable and
* makes sure is it defined in <code><b>IN</b></code> or <code><b>IN</b></code> expression.
*
* @param expression The {@link IndexExpression} to validate
*/
protected boolean validateIndexExpression(IndexExpression expression) {
boolean valid = true;
// The INDEX function can only be applied to identification variables denoting types for
// which an order column has been specified
String variableName = literal(
expression.getExpression(),
LiteralType.IDENTIFICATION_VARIABLE
);
// The identification variable is not defined in a JOIN or IN expression
if (ExpressionTools.stringIsNotEmpty(variableName) &&
!helper.isCollectionIdentificationVariable(variableName)) {
addProblem(expression.getExpression(), IndexExpression_WrongVariable, variableName);
valid = false;
}
return valid;
}
/**
* Validates the given {@link InExpression}. The test to perform is:
* <ul>
* <li>If the expression is a path expression, validation makes sure it is an association mapping,
* a basic field is not allowed.</li>
* </ul>
*
* @param expression The {@link InExpression} to validate
*/
protected void validateInExpression(InExpression expression) {
// Validate the left expression
if (expression.hasExpression()) {
Expression stringExpression = expression.getExpression();
// Special case for state field path expression
StateFieldPathExpression pathExpression = getStateFieldPathExpression(stringExpression);
if (pathExpression != null) {
validateStateFieldPathExpression(pathExpression, validPathExpressionTypeForInExpression());
}
else {
stringExpression.accept(this);
}
}
// Validate the items
expression.getInItems().accept(getInItemsVisitor());
}
/**
* Validates the given {@link Join}.
*
* @param expression The {@link ValueExpression} to validate
*/
protected void validateJoin(Join expression) {
if (expression.hasJoinAssociationPath()) {
Expression joinAssociationPath = expression.getJoinAssociationPath();
validateJoinCollectionValuedPathExpression(joinAssociationPath, false);
joinAssociationPath.accept(this);
}
if (expression.hasIdentificationVariable()) {
try {
registerIdentificationVariable = false;
expression.getIdentificationVariable().accept(this);
}
finally {
registerIdentificationVariable = true;
}
}
}
protected void validateJoinsIdentificationVariable(AbstractFromClause expression,
List<? extends JPQLQueryDeclaration> declarations,
JPQLQueryDeclaration declaration,
int index) {
List<Join> joins = declaration.getJoins();
for (int joinIndex = 0, joinCount = joins.size(); joinIndex < joinCount; joinIndex++) {
Join join = joins.get(joinIndex);
// Retrieve the identification variable from the join association path
String variableName = literal(
join.getJoinAssociationPath(),
LiteralType.PATH_EXPRESSION_IDENTIFICATION_VARIABLE
);
// Make sure the identification variable is defined before the JOIN expression
if (ExpressionTools.stringIsNotEmpty(variableName) &&
isIdentificationVariableDeclaredAfter(variableName, index, joinIndex, declarations)) {
int startPosition = position(join.getJoinAssociationPath());
int endPosition = startPosition + variableName.length();
addProblem(
expression,
startPosition,
endPosition,
AbstractFromClause_WrongOrderOfIdentificationVariableDeclaration,
variableName
);
}
}
}
/**
* Validates the given {@link KeyExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link KeyExpression} to validate
*/
protected void validateKeyExpression(KeyExpression expression) {
super.visit(expression);
}
/**
* Validates the encapsulated expression of the given <code><b>LENGTH</b></code> expression. The
* test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link LengthExpression} to validate by validating its encapsulated expression
* @return <code>false</code> if the encapsulated expression was validated and is invalid;
* <code>true</code> otherwise
*/
protected boolean validateLengthExpression(LengthExpression expression) {
return validateFunctionPathExpression(expression);
}
/**
* Validates the string expression of the given <code><b>LIKE</b></code> expression. The test to
* perform is:
* <ul>
* <li>If the string expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link LengthExpression} to validate by validating its string expression
* @return A number indicating the validation result. {@link #isValid(int, int)} can be used to
* determine the validation status of an expression based on its position
*/
protected int validateLikeExpression(LikeExpression expression) {
int result = 0;
// Validate the "first" expression
if (expression.hasStringExpression()) {
Expression stringExpression = expression.getStringExpression();
// Special case for state field path expression, association field is not allowed
StateFieldPathExpression pathExpression = getStateFieldPathExpression(stringExpression);
if (pathExpression != null) {
boolean valid = validateStateFieldPathExpression(pathExpression, validPathExpressionTypeForStringExpression());
updateStatus(result, 0, valid);
}
else {
stringExpression.accept(this);
}
}
// Validate the pattern value
expression.getPatternValue().accept(this);
// Validate the escape character
expression.getEscapeCharacter().accept(this);
return result;
}
/**
* Validates the encapsulated expression of the given <code><b>LOCATE</b></code> expression. The
* test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link LocateExpression} to validate by validating its encapsulated expression
* @return A number indicating the validation result. {@link #isValid(int, int)} can be used to
* determine the validation status of an expression based on its position
*/
protected int validateLocateExpression(LocateExpression expression) {
int result = 0;
// Validate the first expression
if (expression.hasFirstExpression()) {
Expression firstExpression = expression.getFirstExpression();
// Special case for state field path expression, association field is not allowed
StateFieldPathExpression pathExpression = getStateFieldPathExpression(firstExpression);
if (pathExpression != null) {
boolean valid = validateStateFieldPathExpression(pathExpression, PathType.BASIC_FIELD_ONLY);
updateStatus(result, 0, valid);
}
else {
firstExpression.accept(this);
}
}
// Validate the second expression
expression.getSecondExpression().accept(this);
// Validate the third expression
expression.getThirdExpression().accept(this);
return result;
}
/**
* Validates the encapsulated expression of the given <code><b>LOWER</b></code> expression. The
* test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link LowerExpression} to validate by validating its encapsulated expression
* @return <code>false</code> if the encapsulated expression was validated and is invalid;
* <code>true</code> otherwise
*/
protected boolean validateLowerExpression(LowerExpression expression) {
return validateFunctionPathExpression(expression);
}
/**
* Validates the encapsulated expression of the given <code><b>MAX</b></code> expression. The
* test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link MaxFunction} to validate by validating its encapsulated expression
* @return <code>false</code> if the encapsulated expression was validated and is not valid;
* <code>true</code> otherwise
*/
protected boolean validateMaxFunction(MaxFunction expression) {
// Arguments to the functions MAX must correspond to orderable state field types (i.e.,
// numeric types, string types, character types, or date types)
return validateFunctionPathExpression(expression);
}
/**
* Validates the encapsulated expression of the given <code><b>MIN</b></code> expression. The
* test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link MinFunction} to validate by validating its encapsulated expression
* @return <code>false</code> if the encapsulated expression was validated and is not valid;
* <code>true</code> otherwise
*/
protected boolean validateMinFunction(MinFunction expression) {
// Arguments to the functions MIN must correspond to orderable state field types (i.e.,
// numeric types, string types, character types, or date types)
return validateFunctionPathExpression(expression);
}
/**
* Validates the encapsulated expression of the given <code><b>MOD</b></code> expression. The
* test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link ModExpression} to validate by validating its encapsulated expression
* @return A number indicating the validation result. {@link #isValid(int, int)} can be used to
* determine the validation status of an expression based on its position
*/
protected int validateModExpression(ModExpression expression) {
int result = 0;
// Validate the first expression
if (expression.hasFirstExpression()) {
Expression firstExpression = expression.getFirstExpression();
// Special case for state field path expression, association field is not allowed
StateFieldPathExpression pathExpression = getStateFieldPathExpression(firstExpression);
if (pathExpression != null) {
boolean valid = validateStateFieldPathExpression(pathExpression, PathType.BASIC_FIELD_ONLY);
updateStatus(result, 0, valid);
}
else {
firstExpression.accept(this);
}
}
// Validate the second expression
expression.getSecondExpression().accept(this);
return result;
}
/**
* Validates the encapsulated expression of the given multiplication expression. The test to
* perform is:
* <ul>
* <li>If the left or right expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the left or right expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be updated.</li>
* </ul>
*
* @param expression The {@link MultiplicationExpression} to validate by validating its encapsulated
* expression
* @return A number indicating the validation result. {@link #isValid(int, int)} can be used to
* determine the validation status of an expression based on its position
*/
protected int validateMultiplicationExpression(MultiplicationExpression expression) {
return validateArithmeticExpression(
expression,
MultiplicationExpression_LeftExpression_WrongType,
MultiplicationExpression_RightExpression_WrongType
);
}
/**
* Validates the given {@link NotExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link NotExpression} to validate
*/
protected void validateNotExpression(NotExpression expression) {
super.visit(expression);
}
/**
* Validates the given {@link NullComparisonExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link NullComparisonExpression} to validate
*/
protected void validateNullComparisonExpression(NullComparisonExpression expression) {
super.visit(expression);
}
/**
* Validates the given {@link NullIfExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link NullIfExpression} to validate
*/
protected void validateNullIfExpression(NullIfExpression expression) {
super.visit(expression);
}
/**
* Validates the given {@link ObjectExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link ObjectExpression} to validate
*/
protected void validateObjectExpression(ObjectExpression expression) {
super.visit(expression);
}
/**
* Validates the given {@link OnClause}. The default behavior does not require to semantically
* validate it.
*
* @param expression The {@link OnClause} to validate
*/
protected void validateOnClause(OnClause expression) {
super.visit(expression);
}
/**
* Validates the given {@link OrderByItem}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link OrderByItem} to validate
*/
protected void validateOrderByClause(OrderByClause expression) {
super.visit(expression);
}
/**
* Validates the given {@link OrderByItem}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link OrderByItem} to validate
*/
protected void validateOrderByItem(OrderByItem expression) {
super.visit(expression);
}
/**
* Validates the given {@link OrExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link OrExpression} to validate
*/
protected void validateOrExpression(OrExpression expression) {
super.visit(expression);
}
/**
* Validates the given {@link RangeVariableDeclaration}.
*
* @param expression The {@link RangeVariableDeclaration} to validate
*/
protected void validateRangeVariableDeclaration(RangeVariableDeclaration expression) {
validateRangeVariableDeclarationRootObject(expression);
try {
registerIdentificationVariable = false;
expression.getIdentificationVariable().accept(this);
}
finally {
registerIdentificationVariable = true;
}
}
/**
* Validates the "root" object of the given {@link RangeVariableDeclaration}.
*
* @param expression The {@link RangeVariableDeclaration} that needs its "root" object
* to be validated
*/
protected void validateRangeVariableDeclarationRootObject(RangeVariableDeclaration expression) {
expression.getRootObject().accept(this);
}
/**
* Validates the given {@link ResultVariable}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link ResultVariable} to validate
*/
protected void validateResultVariable(ResultVariable expression) {
// TODO: Validate identification to make sure it does not
// collide with one defined in the FROM clause
super.visit(expression);
}
/**
* Validates the given {@link SelectClause}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link SelectClause} to validate
*/
protected void validateSelectClause(SelectClause expression) {
Expression selectExpression = expression.getSelectExpression();
// Special case for state field path expression, all types are allowed
StateFieldPathExpression pathExpression = getStateFieldPathExpression(selectExpression);
if (pathExpression != null) {
validateStateFieldPathExpression(pathExpression, selectClausePathExpressionPathType());
}
else {
selectExpression.accept(this);
}
}
/**
* Validates the given {@link SelectStatement}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link SelectStatement} to validate
*/
protected void validateSelectStatement(SelectStatement expression) {
super.visit(expression);
}
/**
* Validates the given {@link SimpleFromClause}.
*
* @param expression The {@link SimpleFromClause} to validate
*/
protected void validateSimpleFromClause(SimpleFromClause expression) {
validateAbstractFromClause(expression, subqueryFirstDeclarationVisitor());
}
/**
* Validates the given {@link SimpleSelectClause}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link SimpleSelectClause} to validate
*/
protected void validateSimpleSelectClause(SimpleSelectClause expression) {
super.visit(expression);
}
protected void validateSimpleSelectStatement(SimpleSelectStatement expression) {
// Keep a copy of the identification variables that are used throughout the parent query
List<IdentificationVariable> oldUsedIdentificationVariables = new ArrayList<>(usedIdentificationVariables);
// Create a context for the subquery
helper.newSubqueryContext(expression);
try {
super.visit(expression);
// Validate the identification variables that are used within the subquery
validateIdentificationVariables();
}
finally {
// Revert back to the parent context
helper.disposeSubqueryContext();
// Revert the list to what it was
usedIdentificationVariables.retainAll(oldUsedIdentificationVariables);
}
}
/**
* Validates the given {@link SizeExpression}.
*
* @param expression The {@link SizeExpression} to validate
* @return <code>false</code> if the encapsulated expression is a collection-valued path expression
* and it was found to be invalid; <code>true</code> otherwise
*/
protected boolean validateSizeExpression(SizeExpression expression) {
// The SIZE function returns an integer value, the number of elements of the collection
return validateCollectionValuedPathExpression(expression.getExpression(), true);
}
/**
* Validates the encapsulated expression of the given <code><b>SQRT</b></code> expression. The
* test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link SqrtExpression} to validate by validating its encapsulated expression
* @return <code>false</code> if the encapsulated expression was validated and is invalid;
* <code>true</code> otherwise
*/
protected boolean validateSqrtExpression(SqrtExpression expression) {
return validateFunctionPathExpression(expression);
}
/**
* Validates the given {@link StateFieldPathExpression}.
*
* @param expression The {@link StateFieldPathExpression} the validate
* @param pathType The type of field that is allowed
* @return <code>true</code> if the given {@link StateFieldPathExpression} resolves to a valid
* path; <code>false</code> otherwise
*/
protected boolean validateStateFieldPathExpression(StateFieldPathExpression expression,
PathType pathType) {
boolean valid = true;
if (expression.hasIdentificationVariable()) {
expression.getIdentificationVariable().accept(this);
// A single_valued_object_field is designated by the name of an association field in a
// one-to-one or many-to-one relationship or a field of embeddable class type
if (!expression.endsWithDot()) {
Object mapping = helper.resolveMapping(expression);
// Validate the mapping
if (mapping != null) {
// It is syntactically illegal to compose a path expression from a path expression
// that evaluates to a collection
if (helper.isCollectionMapping(mapping)) {
if (pathType != PathType.ANY_FIELD_INCLUDING_COLLECTION) {
addProblem(expression, StateFieldPathExpression_CollectionType, expression.toActualText());
valid = false;
}
}
// A transient mapping is not allowed
else if (helper.isTransient(mapping)) {
addProblem(expression, StateFieldPathExpression_NoMapping, expression.toParsedText());
valid = false;
}
// Only a basic field is allowed
else if ((pathType == PathType.BASIC_FIELD_ONLY) &&
!helper.isPropertyMapping(mapping)) {
addProblem(expression, StateFieldPathExpression_AssociationField, expression.toActualText());
valid = false;
}
// Only an association field is allowed
else if ((pathType == PathType.ASSOCIATION_FIELD_ONLY) &&
helper.isPropertyMapping(mapping)) {
addProblem(expression, StateFieldPathExpression_BasicField, expression.toActualText());
valid = false;
}
}
// Attempts to resolve something that does not represent a mapping
else {
Object type = helper.getType(expression.toParsedText(0, expression.pathSize() - 1));
// An enum constant could have been parsed as a state field path expression
if (helper.isEnumType(type)) {
// Search for the enum constant
String enumConstant = expression.getPath(expression.pathSize() - 1);
boolean found = false;
for (String constant : helper.getEnumConstants(type)) {
if (constant.equals(enumConstant)) {
found = true;
break;
}
}
if (!found) {
int startIndex = position(expression) + helper.getTypeName(type).length() + 1;
int endIndex = startIndex + enumConstant.length();
addProblem(expression, startIndex, endIndex, StateFieldPathExpression_InvalidEnumConstant, enumConstant);
valid = false;
}
// Remove the used identification variable since it is the first
// package name of the fully qualified enum constant
usedIdentificationVariables.remove(expression.getIdentificationVariable());
}
else {
// Special case for EclipseLink, path expression formed with the identification variable
// mapped to a subquery or database table cannot be resolved, thus cannot be validated
Boolean status = validateThirdPartyStateFieldPathExpression(expression);
// Third path extension validated the path expression
if (status != null) {
valid = status;
}
// Third path extension did not validate the path expression, continue validating it
else {
type = helper.getType(expression);
// Does not resolve to a valid path
if (!helper.isTypeResolvable(type)) {
addProblem(expression, StateFieldPathExpression_NotResolvable, expression.toActualText());
valid = false;
}
// No mapping can be found for that path
else {
addProblem(expression, StateFieldPathExpression_NoMapping, expression.toActualText());
valid = false;
}
}
}
}
}
}
return valid;
}
/**
* Validates the encapsulated expression of the given <code><b>SUBSTRING</b></code> expression.
* The test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link SubstringExpression} to validate by validating its encapsulated expression
* @return A number indicating the validation result. {@link #isValid(int, int)} can be used to
* determine the validation status of an expression based on its position
*/
protected int validateSubstringExpression(SubstringExpression expression) {
int result = 0;
// Validate the first expression
if (expression.hasFirstExpression()) {
Expression firstExpression = expression.getFirstExpression();
// Special case for state field path expression, association field is not allowed
StateFieldPathExpression pathExpression = getStateFieldPathExpression(firstExpression);
if (pathExpression != null) {
boolean valid = validateStateFieldPathExpression(pathExpression, PathType.BASIC_FIELD_ONLY);
updateStatus(result, 0, valid);
}
else {
firstExpression.accept(this);
}
}
// Validate the second expression
expression.getSecondExpression().accept(this);
// Validate the third expression
expression.getThirdExpression().accept(this);
return result;
}
/**
* Validates the encapsulated expression of the given subtraction expression. The test to perform is:
* <ul>
* <li>If the left or right expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the left or right expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be updated.</li>
* </ul>
*
* @param expression The {@link SubtractionExpression} to validate by validating its encapsulated
* expression
* @return A number indicating the validation result. {@link #isValid(int, int)} can be used to
* determine the validation status of an expression based on its position
*/
protected int validateSubtractionExpression(SubtractionExpression expression) {
return validateArithmeticExpression(
expression,
SubtractionExpression_LeftExpression_WrongType,
SubtractionExpression_RightExpression_WrongType
);
}
/**
* Validates the encapsulated expression of the given <code><b>MOD</b></code> expression. The
* test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link ModExpression} to validate by validating its encapsulated expression
* @return <code>false</code> if the encapsulated expression was validated and is invalid;
* <code>true</code> otherwise
*/
protected boolean validateSumFunction(SumFunction expression) {
return validateFunctionPathExpression(expression);
}
/**
* Validates the given {@link StateFieldPathExpression}, which means the path does not represent
* a mapping, or an enum constant.
*
* @param expression The {@link StateFieldPathExpression} the validate
* @return <code>Boolean.TRUE</code> or <code>Boolean.FALSE</code> if the given {@link
* StateFieldPathExpression} was validated by this method; <code>null</code> if it could not be
* validated (as being valid or invalid)
*/
protected Boolean validateThirdPartyStateFieldPathExpression(StateFieldPathExpression expression) {
return null;
}
/**
* Validates the given {@link TreatExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link TreatExpression} to validate
*/
protected void validateTreatExpression(TreatExpression expression) {
super.visit(expression);
}
/**
* Validates the encapsulated expression of the given <code><b>TRIM</b></code> expression. The
* test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link TrimExpression} to validate by validating its encapsulated expression
* @return <code>false</code> if the encapsulated expression was validated and is invalid;
* <code>true</code> otherwise
*/
protected boolean validateTrimExpression(TrimExpression expression) {
return validateFunctionPathExpression(expression);
}
/**
* Validates the given {@link TypeExpression}. The test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is an
* association field, a basic field is not allowed.</li>
* </ul>
*
* @param expression The {@link TypeExpression} to validate
* @return <code>false</code> if the encapsulated expression was validated and is invalid;
* <code>true</code> otherwise
*/
protected boolean validateTypeExpression(TypeExpression expression) {
// Validate the expression
if (expression.hasEncapsulatedExpression()) {
Expression encapsulatedExpression = expression.getExpression();
// Special case for state field path expression, only association field is allowed
StateFieldPathExpression pathExpression = getStateFieldPathExpression(encapsulatedExpression);
if (pathExpression != null) {
return validateStateFieldPathExpression(pathExpression, PathType.ASSOCIATION_FIELD_ONLY);
}
else {
encapsulatedExpression.accept(this);
}
}
return true;
}
/**
* Validates the given {@link UpdateClause}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link UpdateClause} to validate
*/
protected void validateUpdateClause(UpdateClause expression) {
super.visit(expression);
}
/**
* Validates the given {@link UpdateItem} by validating the traversability of the path
* expression. The path expression is valid if it follows one of the following rules:
* <ul>
* <li>The identification variable is omitted if it's not defined in the <b>FROM</b> clause;
* <li>The last path is a state field;
* <li>Only embedded field can be traversed.
* </ul>
*
* @param expression {@link UpdateItem} to validate its path expression
* @return <code>true</code> if the path expression is valid; <code>false</code> otherwise
*/
protected boolean validateUpdateItem(UpdateItem expression) {
boolean valid = true;
if (expression.hasStateFieldPathExpression()) {
// Retrieve the state field path expression
StateFieldPathExpression pathExpression = getStateFieldPathExpression(expression.getStateFieldPathExpression());
if ((pathExpression != null) &&
(pathExpression.hasIdentificationVariable() ||
pathExpression.hasVirtualIdentificationVariable())) {
// Start traversing the path expression by first retrieving the managed type
Object managedType = helper.getManagedType(pathExpression.getIdentificationVariable());
if (managedType != null) {
// Continue to traverse the path expression
for (int index = pathExpression.hasVirtualIdentificationVariable() ? 0 : 1, count = pathExpression.pathSize(); index < count; index++) {
// Retrieve the mapping
String path = pathExpression.getPath(index);
Object mapping = helper.getMappingNamed(managedType, path);
// Not resolvable
if (mapping == null) {
addProblem(pathExpression, UpdateItem_NotResolvable, pathExpression.toParsedText());
valid = false;
break;
}
// A collection mapping cannot be traversed or a value cannot be assigned to it
else if (helper.isCollectionMapping(mapping)) {
addProblem(pathExpression, UpdateItem_RelationshipPathExpression);
valid = false;
break;
}
// Validate an intermediate path (n + 1, ..., n - 2)
else if (index + 1 < count) {
// A relationship mapping cannot be traversed
if (helper.isRelationshipMapping(mapping)) {
addProblem(pathExpression, UpdateItem_RelationshipPathExpression);
valid = false;
break;
}
// A basic mapping cannot be traversed
else if (helper.isPropertyMapping(mapping)) {
addProblem(pathExpression, UpdateItem_RelationshipPathExpression);
valid = false;
break;
}
// Retrieve the embeddable mapping's reference managed type
else {
managedType = helper.getReferenceManagedType(mapping);
if (managedType == null) {
addProblem(pathExpression, UpdateItem_NotResolvable, pathExpression.toParsedText());
valid = false;
break;
}
}
}
}
}
else {
addProblem(pathExpression, UpdateItem_NotResolvable, pathExpression.toParsedText());
valid = false;
}
}
else {
addProblem(pathExpression, UpdateItem_NotResolvable, expression.getStateFieldPathExpression().toParsedText());
valid = false;
}
}
return valid;
}
/**
* Validates the given {@link UpdateStatement}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link UpdateStatement} to validate
*/
protected void validateUpdateStatement(UpdateStatement expression) {
super.visit(expression);
}
/**
* Validates the encapsulated expression of the given <code><b>UPPER</b></code> expression. The
* test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link UpperExpression} to validate by validating its encapsulated expression
* @return <code>false</code> if the encapsulated expression was validated and is invalid;
* <code>true</code> otherwise
*/
protected boolean validateUpperExpression(UpperExpression expression) {
return validateFunctionPathExpression(expression);
}
/**
* Validates the given {@link ValueExpression}. The default behavior does not require to
* semantically validate it.
*
* @param expression The {@link ValueExpression} to validate
*/
protected void validateValueExpression(ValueExpression expression) {
super.visit(expression);
}
/**
* Validates the given {@link WhenClause}. The default behavior does not require to semantically
* validate it.
*
* @param expression The {@link WhenClause} to validate
*/
protected void validateWhenClause(WhenClause expression) {
super.visit(expression);
}
/**
* Validates the given {@link WhereClause}. The default behavior does not require to semantically
* validate it.
*
* @param expression The {@link WhereClause} to validate
*/
protected void validateWhereClause(WhereClause expression) {
super.visit(expression);
}
/**
* Returns the type of path expression that is valid for a count function; which is the left
* expression in a <code><b>COUNT</b></code> expression.
*
* @return By default, any field are allowed except collection
*/
protected PathType validPathExpressionTypeForCountFunction() {
return PathType.ANY_FIELD;
}
/**
* Returns the type of path expression that is valid for the expression being tested by an
* <code><b>IN</b></code> expression; which is the left expression.
*
* @return By default, any field (without collection) is allowed
*/
protected PathType validPathExpressionTypeForInExpression() {
return PathType.ANY_FIELD;
}
/**
* Returns the type of path expression that is valid for an <code>IN</code> items; which is the
* left expression in a <code><b>IN</b></code> expression.
*
* @return By default, any field are allowed except collection
*/
protected PathType validPathExpressionTypeForInItem() {
return PathType.ANY_FIELD;
}
/**
* Returns the type of path expression that is valid for a string expression; which is the left
* expression in a <code><b>LIKE</b></code> expression.
*
* @return By default, only basic field are allowed
*/
protected PathType validPathExpressionTypeForStringExpression() {
return PathType.BASIC_FIELD_ONLY;
}
@Override
public final void visit(AbsExpression expression) {
validateAbsExpression(expression);
}
@Override
public final void visit(AbstractSchemaName expression) {
validateAbstractSchemaName(expression);
}
@Override
public final void visit(AdditionExpression expression) {
validateAdditionExpression(expression);
}
@Override
public final void visit(AllOrAnyExpression expression) {
validateAllOrAnyExpression(expression);
}
@Override
public final void visit(AndExpression expression) {
validateAndExpression(expression);
}
@Override
public final void visit(ArithmeticFactor expression) {
validateArithmeticExpression(expression);
}
@Override
public final void visit(AvgFunction expression) {
validateAvgFunction(expression);
}
@Override
public final void visit(BadExpression expression) {
// Nothing to validate semantically
}
@Override
public final void visit(BetweenExpression expression) {
validateBetweenExpression(expression);
}
@Override
public final void visit(CaseExpression expression) {
validateCaseExpression(expression);
}
@Override
public final void visit(CoalesceExpression expression) {
validateCoalesceExpression(expression);
}
@Override
public final void visit(CollectionExpression expression) {
// Nothing to validate semantically
super.visit(expression);
}
@Override
public final void visit(CollectionMemberDeclaration expression) {
validateCollectionMemberDeclaration(expression);
}
@Override
public final void visit(CollectionMemberExpression expression) {
validateCollectionMemberExpression(expression);
}
@Override
public final void visit(CollectionValuedPathExpression expression) {
// Validated by the parent of the expression
}
@Override
public final void visit(ComparisonExpression expression) {
validateComparisonExpression(expression);
}
@Override
public final void visit(ConcatExpression expression) {
validateConcatExpression(expression);
}
@Override
public final void visit(ConstructorExpression expression) {
validateConstructorExpression(expression);
}
@Override
public final void visit(CountFunction expression) {
validateCountFunction(expression);
}
@Override
public final void visit(DateTime expression) {
validateDateTime(expression);
}
@Override
public final void visit(DeleteClause expression) {
validateDeleteClause(expression);
}
@Override
public final void visit(DeleteStatement expression) {
validateDeleteStatement(expression);
}
@Override
public final void visit(DivisionExpression expression) {
validateDivisionExpression(expression);
}
@Override
public final void visit(EmptyCollectionComparisonExpression expression) {
validateCollectionValuedPathExpression(expression.getExpression(), true);
}
@Override
public final void visit(EntityTypeLiteral expression) {
validateEntityTypeLiteral(expression);
}
@Override
public final void visit(EntryExpression expression) {
validateEntryExpression(expression);
}
@Override
public final void visit(ExistsExpression expression) {
validateExistsExpression(expression);
}
@Override
public final void visit(FromClause expression) {
validateFromClause(expression);
}
@Override
public final void visit(FunctionExpression expression) {
validateFunctionExpression(expression);
}
@Override
public final void visit(GroupByClause expression) {
validateGroupByClause(expression);
}
@Override
public final void visit(HavingClause expression) {
validateHavingClause(expression);
}
@Override
public final void visit(IdentificationVariable expression) {
validateIdentificationVariable(expression);
}
@Override
public final void visit(IdentificationVariableDeclaration expression) {
validateIdentificationVariableDeclaration(expression);
}
@Override
public final void visit(IndexExpression expression) {
validateIndexExpression(expression);
}
@Override
public final void visit(InExpression expression) {
validateInExpression(expression);
}
@Override
public final void visit(InputParameter expression) {
// Nothing to validate semantically
}
@Override
public final void visit(Join expression) {
validateJoin(expression);
}
@Override
public final void visit(JPQLExpression expression) {
if (expression.hasQueryStatement()) {
expression.getQueryStatement().accept(this);
validateIdentificationVariables();
}
}
@Override
public final void visit(KeyExpression expression) {
validateKeyExpression(expression);
}
@Override
public final void visit(KeywordExpression expression) {
// Nothing semantically to validate
}
@Override
public final void visit(LengthExpression expression) {
validateLengthExpression(expression);
}
@Override
public final void visit(LikeExpression expression) {
validateLikeExpression(expression);
}
@Override
public final void visit(LocateExpression expression) {
validateLocateExpression(expression);
}
@Override
public final void visit(LowerExpression expression) {
validateLowerExpression(expression);
}
@Override
public final void visit(MaxFunction expression) {
validateMaxFunction(expression);
}
@Override
public final void visit(MinFunction expression) {
validateMinFunction(expression);
}
@Override
public final void visit(ModExpression expression) {
validateModExpression(expression);
}
@Override
public final void visit(MultiplicationExpression expression) {
validateMultiplicationExpression(expression);
}
@Override
public final void visit(NotExpression expression) {
validateNotExpression(expression);
}
@Override
public final void visit(NullComparisonExpression expression) {
validateNullComparisonExpression(expression);
}
@Override
public final void visit(NullExpression expression) {
// Nothing semantically to validate
}
@Override
public final void visit(NullIfExpression expression) {
validateNullIfExpression(expression);
}
@Override
public final void visit(NumericLiteral expression) {
// Nothing semantically to validate
}
@Override
public final void visit(ObjectExpression expression) {
validateObjectExpression(expression);
}
@Override
public final void visit(OnClause expression) {
validateOnClause(expression);
}
@Override
public final void visit(OrderByClause expression) {
validateOrderByClause(expression);
}
@Override
public final void visit(OrderByItem expression) {
validateOrderByItem(expression);
}
@Override
public final void visit(OrExpression expression) {
validateOrExpression(expression);
}
@Override
public final void visit(RangeVariableDeclaration expression) {
validateRangeVariableDeclaration(expression);
}
@Override
public final void visit(ResultVariable expression) {
try {
registerIdentificationVariable = false;
validateResultVariable(expression);
}
finally {
registerIdentificationVariable = true;
}
}
@Override
public final void visit(SelectClause expression) {
validateSelectClause(expression);
}
@Override
public final void visit(SelectStatement expression) {
validateSelectStatement(expression);
}
@Override
public final void visit(SimpleFromClause expression) {
validateSimpleFromClause(expression);
}
@Override
public final void visit(SimpleSelectClause expression) {
validateSimpleSelectClause(expression);
}
@Override
public final void visit(SimpleSelectStatement expression) {
validateSimpleSelectStatement(expression);
}
@Override
public final void visit(SizeExpression expression) {
validateSizeExpression(expression);
}
@Override
public final void visit(SqrtExpression expression) {
validateSqrtExpression(expression);
}
@Override
public final void visit(StateFieldPathExpression expression) {
validateStateFieldPathExpression(expression, PathType.ANY_FIELD);
}
@Override
public final void visit(StringLiteral expression) {
// Nothing semantically to validate
}
@Override
public final void visit(SubExpression expression) {
// Nothing semantically to validate
super.visit(expression);
}
@Override
public final void visit(SubstringExpression expression) {
validateSubstringExpression(expression);
}
@Override
public final void visit(SubtractionExpression expression) {
validateSubtractionExpression(expression);
}
@Override
public final void visit(SumFunction expression) {
validateSumFunction(expression);
}
@Override
public final void visit(TreatExpression expression) {
validateTreatExpression(expression);
}
@Override
public final void visit(TrimExpression expression) {
validateTrimExpression(expression);
}
@Override
public final void visit(TypeExpression expression) {
validateTypeExpression(expression);
}
@Override
public final void visit(UnknownExpression expression) {
// Nothing semantically to validate
}
@Override
public final void visit(UpdateClause expression) {
validateUpdateClause(expression);
}
@Override
public final void visit(UpdateItem expression) {
validateUpdateItem(expression);
}
@Override
public final void visit(UpdateStatement expression) {
validateUpdateStatement(expression);
}
@Override
public final void visit(UpperExpression expression) {
validateUpperExpression(expression);
}
@Override
public final void visit(ValueExpression expression) {
validateValueExpression(expression);
}
@Override
public final void visit(WhenClause expression) {
validateWhenClause(expression);
}
@Override
public final void visit(WhereClause expression) {
validateWhereClause(expression);
}
// Made static final for performance reasons.
/**
* This visitor is meant to retrieve an {@link CollectionValuedPathExpression} if the visited
* {@link Expression} is that object.
*/
protected static final class CollectionValuedPathExpressionVisitor extends AbstractExpressionVisitor {
/**
* The {@link CollectionValuedPathExpression} that was visited; <code>null</code> if he was not.
*/
protected CollectionValuedPathExpression expression;
/**
* Default constructor.
*/
protected CollectionValuedPathExpressionVisitor() {
}
@Override
public void visit(CollectionValuedPathExpression expression) {
this.expression = expression;
}
}
// Made static final for performance reasons.
protected static final class ComparingEntityTypeLiteralVisitor extends AbstractExpressionVisitor {
protected IdentificationVariable expression;
public boolean result;
/**
* Default constructor.
*/
protected ComparingEntityTypeLiteralVisitor() {
}
@Override
public void visit(ComparisonExpression expression) {
result = true;
}
@Override
public void visit(IdentificationVariable expression) {
if (this.expression == expression) {
expression.getParent().accept(this);
}
}
@Override
public void visit(SubExpression expression) {
// Make sure to bypass any sub expression
expression.getParent().accept(this);
}
}
// Made static final for performance reasons.
/**
* This visitor compares the left and right expressions of a comparison expression and gathers
* information about those expressions if they are an identification variable or a path expression.
*/
protected static final class ComparisonExpressionVisitor extends AnonymousExpressionVisitor {
private final AbstractSemanticValidator validator;
public boolean leftIdentificationVariable;
public boolean leftIdentificationVariableValid;
public boolean leftStateFieldPathExpression;
public boolean leftStateFieldPathExpressionValid;
public boolean rightIdentificationVariable;
public boolean rightIdentificationVariableValid;
public boolean rightStateFieldPathExpression;
public boolean rightStateFieldPathExpressionValid;
public boolean validatingLeftExpression;
private ComparisonExpressionVisitor(AbstractSemanticValidator validator) {
this.validator = validator;
}
/**
* Resets the flags.
*/
private void dispose() {
leftIdentificationVariable = false;
leftIdentificationVariableValid = false;
leftStateFieldPathExpression = false;
leftStateFieldPathExpressionValid = false;
rightIdentificationVariable = false;
rightIdentificationVariableValid = false;
rightStateFieldPathExpression = false;
rightStateFieldPathExpressionValid = false;
}
@Override
protected void visit(Expression expression) {
// Redirect to the validator, nothing special is required
expression.accept(validator);
}
@Override
public void visit(IdentificationVariable expression) {
// Make sure the identification variable is not a result variable
if (!validator.helper.isResultVariable(expression.getVariableName())) {
if (validatingLeftExpression) {
leftIdentificationVariable = !expression.isVirtual();
// Make sure what was parsed is a valid identification variable
leftIdentificationVariableValid = validator.validateIdentificationVariable(expression);
}
else {
rightIdentificationVariable = !expression.isVirtual();
// Make sure what was parsed is a valid identification variable
rightIdentificationVariableValid = validator.validateIdentificationVariable(expression);
}
}
}
@Override
public void visit(StateFieldPathExpression expression) {
if (validatingLeftExpression) {
leftStateFieldPathExpression = true;
// Make sure what was parsed is a valid path expression
leftStateFieldPathExpressionValid = validator.validateStateFieldPathExpression(
expression,
PathType.ANY_FIELD_INCLUDING_COLLECTION
);
}
else {
rightStateFieldPathExpression = true;
// Make sure what was parsed is a valid path expression
rightStateFieldPathExpressionValid = validator.validateStateFieldPathExpression(
expression,
PathType.ANY_FIELD_INCLUDING_COLLECTION
);
}
}
}
// Made static for performance reasons.
protected static class FirstDeclarationVisitor extends AnonymousExpressionVisitor {
protected boolean valid;
/**
* Default constructor.
*/
protected FirstDeclarationVisitor() {
}
@Override
public void visit(AbstractSchemaName expression) {
valid = true;
}
@Override
public void visit(BadExpression expression) {
// Already validated, no need to duplicate the issue
valid = false;
}
@Override
protected void visit(Expression expression) {
valid = false;
}
@Override
public void visit(IdentificationVariableDeclaration expression) {
expression.getRangeVariableDeclaration().accept(this);
}
@Override
public void visit(NullExpression expression) {
// Already validated, no need to duplicate the issue
valid = false;
}
@Override
public void visit(RangeVariableDeclaration expression) {
expression.getRootObject().accept(this);
}
}
// Made static final for performance reasons.
protected static final class InItemsVisitor extends AnonymousExpressionVisitor {
private final AbstractSemanticValidator validator;
protected InItemsVisitor(AbstractSemanticValidator validator) {
this.validator = validator;
}
@Override
protected void visit(Expression expression) {
expression.accept(validator);
}
@Override
public void visit(StateFieldPathExpression expression) {
validator.validateStateFieldPathExpression(expression, validator.validPathExpressionTypeForInItem());
}
}
// Made static for performance reasons.
/**
* This enumeration allows {@link AbstractSemanticValidator#validateStateFieldPathExpression(
* StateFieldPathExpression, PathType)} to validate the type of the mapping and to make sure it
* is allowed based on its location.
*/
protected static enum PathType {
/**
* This will allow basic, and association fields to be specified.
*/
ANY_FIELD,
/**
* This will allow basic, and association fields to be specified.
*/
ANY_FIELD_INCLUDING_COLLECTION,
/**
* This will allow association fields to be specified but basic mappings are not valid.
*/
ASSOCIATION_FIELD_ONLY,
/**
* This will allow basic fields to be specified but association mappings are not valid.
*/
BASIC_FIELD_ONLY
}
// Made static final for performance reasons.
/**
* This visitor is meant to retrieve an {@link StateFieldPathExpressionVisitor} if the visited
* {@link Expression} is that object.
*/
protected static final class StateFieldPathExpressionVisitor extends AbstractExpressionVisitor {
/**
* The {@link StateFieldPathExpression} that was visited; <code>null</code> if it was not.
*/
protected StateFieldPathExpression expression;
/**
* Default constructor.
*/
protected StateFieldPathExpressionVisitor() {
}
@Override
public void visit(StateFieldPathExpression expression) {
this.expression = expression;
}
}
// Made static final for performance reasons.
protected static final class SubqueryFirstDeclarationVisitor extends FirstDeclarationVisitor {
/**
* Default constructor.
*/
protected SubqueryFirstDeclarationVisitor() {
}
@Override
public void visit(CollectionValuedPathExpression expression) {
valid = true;
}
}
// Made static for performance reasons.
protected static class TopLevelFirstDeclarationVisitor extends FirstDeclarationVisitor {
/**
* Default constructor.
*/
protected TopLevelFirstDeclarationVisitor() {
}
@Override
public void visit(AbstractSchemaName expression) {
// TODO
valid = true;
}
}
}