blob: 664b969c383d9fb4b175673052ca78eeb240d7ff [file] [log] [blame]
/*
* Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation
//
package org.eclipse.persistence.jpa.jpql;
import org.eclipse.persistence.jpa.jpql.AbstractEclipseLinkSemanticValidator.EclipseLinkOwningClauseVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractEclipseLinkExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSelectClause;
import org.eclipse.persistence.jpa.jpql.parser.AsOfClause;
import org.eclipse.persistence.jpa.jpql.parser.CastExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConnectByClause;
import org.eclipse.persistence.jpa.jpql.parser.DatabaseType;
import org.eclipse.persistence.jpa.jpql.parser.DatabaseTypeFactory;
import org.eclipse.persistence.jpa.jpql.parser.DefaultEclipseLinkJPQLGrammar;
import org.eclipse.persistence.jpa.jpql.parser.EclipseLinkExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.Expression;
import org.eclipse.persistence.jpa.jpql.parser.ExtractExpression;
import org.eclipse.persistence.jpa.jpql.parser.HierarchicalQueryClause;
import org.eclipse.persistence.jpa.jpql.parser.InExpression;
import org.eclipse.persistence.jpa.jpql.parser.InputParameter;
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar;
import org.eclipse.persistence.jpa.jpql.parser.OrderSiblingsByClause;
import org.eclipse.persistence.jpa.jpql.parser.PatternValueBNF;
import org.eclipse.persistence.jpa.jpql.parser.RegexpExpression;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.StartWithClause;
import org.eclipse.persistence.jpa.jpql.parser.StringExpressionBNF;
import org.eclipse.persistence.jpa.jpql.parser.TableExpression;
import org.eclipse.persistence.jpa.jpql.parser.TableVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.UnionClause;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.*;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.*;
/**
* This validator adds EclipseLink extension over what the JPA functional specification had defined.
* <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.
*
* @version 2.5
* @since 2.4
* @author Pascal Filion
*/
public class EclipseLinkGrammarValidator extends AbstractGrammarValidator
implements EclipseLinkExpressionVisitor {
private InExpressionVisitor inExpressionVisitor;
private InExpressionWithNestedArrayVisitor inExpressionWithNestedArrayVisitor;
/**
* Creates a new <code>EclipseLinkGrammarValidator</code>.
*
* @param jpqlGrammar The {@link JPQLGrammar} that defines how the JPQL query was parsed
*/
public EclipseLinkGrammarValidator(JPQLGrammar jpqlGrammar) {
super(jpqlGrammar);
}
protected AbstractSingleEncapsulatedExpressionHelper<CastExpression> buildCastExpressionHelper() {
return new AbstractSingleEncapsulatedExpressionHelper<CastExpression>(this) {
@Override
protected String encapsulatedExpressionInvalidKey(CastExpression expression) {
return CastExpression_InvalidExpression;
}
@Override
protected String encapsulatedExpressionMissingKey(CastExpression expression) {
return CastExpression_MissingExpression;
}
@Override
public String leftParenthesisMissingKey(CastExpression expression) {
return CastExpression_MissingLeftParenthesis;
}
@Override
public String rightParenthesisMissingKey(CastExpression expression) {
return CastExpression_MissingRightParenthesis;
}
};
}
protected AbstractDoubleEncapsulatedExpressionHelper<DatabaseType> buildDatabaseTypeHelper() {
return new AbstractDoubleEncapsulatedExpressionHelper<DatabaseType>(this) {
@Override
protected String firstExpressionInvalidKey() {
return DatabaseType_InvalidFirstExpression;
}
@Override
protected String firstExpressionMissingKey() {
return DatabaseType_MissingFirstExpression;
}
@Override
protected boolean hasComma(DatabaseType expression) {
// If the second expression is not specified, then the comma is not needed
return expression.hasComma() ||
!expression.hasSecondExpression();
}
@Override
protected boolean hasFirstExpression(DatabaseType expression) {
return !expression.hasLeftParenthesis() ||
expression.hasFirstExpression();
}
@Override
public boolean hasLeftParenthesis(DatabaseType expression) {
if (expression.hasLeftParenthesis()) {
return true;
}
// The parenthesis are optional unless one the following
// items is specified, then '(' is required
return !(expression.hasFirstExpression() ||
expression.hasComma() ||
expression.hasSecondExpression() ||
expression.hasRightParenthesis());
}
@Override
public boolean hasRightParenthesis(DatabaseType expression) {
if (expression.hasRightParenthesis()) {
return true;
}
// The parenthesis are optional unless one the following
// items is specified, then ')' is required
return !(expression.hasLeftParenthesis() ||
expression.hasFirstExpression() ||
expression.hasComma() ||
expression.hasSecondExpression());
}
@Override
protected boolean hasSecondExpression(DatabaseType expression) {
return !expression.hasComma() ||
expression.hasSecondExpression();
}
@Override
public String leftParenthesisMissingKey(DatabaseType expression) {
return DatabaseType_MissingLeftParenthesis;
}
@Override
protected String missingCommaKey() {
return DatabaseType_MissingComma;
}
@Override
public String rightParenthesisMissingKey(DatabaseType expression) {
return DatabaseType_MissingRightParenthesis;
}
@Override
protected String secondExpressionInvalidKey() {
return DatabaseType_InvalidSecondExpression;
}
@Override
protected String secondExpressionMissingKey() {
return DatabaseType_MissingSecondExpression;
}
};
}
protected AbstractSingleEncapsulatedExpressionHelper<ExtractExpression> buildExtractExpressionHelper() {
return new AbstractSingleEncapsulatedExpressionHelper<ExtractExpression>(this) {
@Override
protected String encapsulatedExpressionInvalidKey(ExtractExpression expression) {
return ExtractExpression_InvalidExpression;
}
@Override
protected String encapsulatedExpressionMissingKey(ExtractExpression expression) {
return ExtractExpression_MissingExpression;
}
@Override
public String leftParenthesisMissingKey(ExtractExpression expression) {
return ExtractExpression_MissingLeftParenthesis;
}
@Override
protected int lengthBeforeEncapsulatedExpression(ExtractExpression expression) {
return expression.getDatePart().length() +
(expression.hasSpaceAfterDatePart() ? 1 : 0) +
(expression.hasFrom() ? 4 /* FROM */ : 0) +
(expression.hasSpaceAfterFrom() ? 1 : 0);
}
@Override
public String rightParenthesisMissingKey(ExtractExpression expression) {
return ExtractExpression_MissingRightParenthesis;
}
};
}
protected InExpressionVisitor buildInExpressionVisitor() {
return new InExpressionVisitor();
}
protected InExpressionWithNestedArrayVisitor buildInExpressionWithNestedArrayVisitor() {
return new InExpressionWithNestedArrayVisitor(this);
}
@Override
protected LiteralVisitor buildLiteralVisitor() {
return new EclipseLinkLiteralVisitor();
}
@Override
protected OwningClauseVisitor buildOwningClauseVisitor() {
return new EclipseLinkOwningClauseVisitor();
}
protected AbstractSingleEncapsulatedExpressionHelper<TableExpression> buildTableExpressionHelper() {
return new AbstractSingleEncapsulatedExpressionHelper<TableExpression>(this) {
@Override
protected String encapsulatedExpressionInvalidKey(TableExpression expression) {
return TableExpression_InvalidExpression;
}
@Override
protected String encapsulatedExpressionMissingKey(TableExpression expression) {
return TableExpression_MissingExpression;
}
@Override
public String leftParenthesisMissingKey(TableExpression expression) {
return TableExpression_MissingLeftParenthesis;
}
@Override
public String rightParenthesisMissingKey(TableExpression expression) {
return TableExpression_MissingRightParenthesis;
}
};
}
protected AbstractSingleEncapsulatedExpressionHelper<CastExpression> castExpressionHelper() {
AbstractSingleEncapsulatedExpressionHelper<CastExpression> helper = getHelper(CAST);
if (helper == null) {
helper = buildCastExpressionHelper();
registerHelper(CAST, helper);
}
return helper;
}
protected AbstractDoubleEncapsulatedExpressionHelper<DatabaseType> databaseTypeHelper() {
AbstractDoubleEncapsulatedExpressionHelper<DatabaseType> helper = getHelper(DatabaseTypeFactory.ID);
if (helper == null) {
helper = buildDatabaseTypeHelper();
registerHelper(DatabaseTypeFactory.ID, helper);
}
return helper;
}
protected AbstractSingleEncapsulatedExpressionHelper<ExtractExpression> extractExpressionHelper() {
AbstractSingleEncapsulatedExpressionHelper<ExtractExpression> helper = getHelper(EXTRACT);
if (helper == null) {
helper = buildExtractExpressionHelper();
registerHelper(EXTRACT, helper);
}
return helper;
}
protected InExpressionVisitor getInExpressionVisitor() {
if (inExpressionVisitor == null) {
inExpressionVisitor = buildInExpressionVisitor();
}
return inExpressionVisitor;
}
protected InExpressionWithNestedArrayVisitor getInExpressionWithNestedArray() {
if (inExpressionWithNestedArrayVisitor == null) {
inExpressionWithNestedArrayVisitor = buildInExpressionWithNestedArrayVisitor();
}
return inExpressionWithNestedArrayVisitor;
}
@Override
protected EclipseLinkOwningClauseVisitor getOwningClauseVisitor() {
return (EclipseLinkOwningClauseVisitor) super.getOwningClauseVisitor();
}
/**
* Determines whether the persistence provider is EclipseLink or not.
*
* @return <code>true</code> if the persistence provider is EclipseLink; <code>false</code> otherwise
*/
protected final boolean isEclipseLink() {
return DefaultEclipseLinkJPQLGrammar.PROVIDER_NAME.equals(getProvider());
}
/**
* Determines whether the subquery is part of an <code><b>IN</b></code> expression where the
* left expression is a nested array.
*
* @param expression The {@link SimpleSelectClause} of the subquery
* @return <code>true</code> if the subquery is in an <code><b>IN</b></code> expression and its
* left expression is a nested array
*/
protected boolean isInExpressionWithNestedArray(SimpleSelectClause expression) {
InExpressionWithNestedArrayVisitor visitor = getInExpressionWithNestedArray();
try {
expression.accept(visitor);
return visitor.valid;
}
finally {
visitor.valid = false;
}
}
@Override
protected boolean isInputParameterInValidLocation(InputParameter expression) {
return true;
}
@Override
protected boolean isJoinFetchIdentifiable() {
EclipseLinkVersion version = EclipseLinkVersion.value(getGrammar().getProviderVersion());
return version.isNewerThanOrEqual(EclipseLinkVersion.VERSION_2_4);
}
@Override
protected boolean isMultipleSubquerySelectItemsAllowed(SimpleSelectClause expression) {
return isInExpressionWithNestedArray(expression);
}
protected boolean isOwnedByInExpression(Expression expression) {
InExpressionVisitor visitor = getInExpressionVisitor();
expression.accept(visitor);
return visitor.expression != null;
}
/**
* Determines whether the given {@link Expression} is a child of the <b>UNION</b> clause.
*
* @param expression The {@link Expression} to visit its parent hierarchy up to the clause
* @return <code>true</code> if the first parent being a clause is the <b>UNION</b> clause;
* <code>false</code> otherwise
*/
protected boolean isOwnedByUnionClause(Expression expression) {
EclipseLinkOwningClauseVisitor visitor = getOwningClauseVisitor();
try {
expression.accept(visitor);
return visitor.unionClause != null;
}
finally {
visitor.dispose();
}
}
@Override
protected boolean isSubqueryAllowedAnywhere() {
EclipseLinkVersion version = EclipseLinkVersion.value(getProviderVersion());
return version.isNewerThanOrEqual(EclipseLinkVersion.VERSION_2_4);
}
protected AbstractSingleEncapsulatedExpressionHelper<TableExpression> tableExpressionHelper() {
AbstractSingleEncapsulatedExpressionHelper<TableExpression> helper = getHelper(TABLE);
if (helper == null) {
helper = buildTableExpressionHelper();
registerHelper(TABLE, helper);
}
return helper;
}
@Override
protected void validateAbstractSelectClause(AbstractSelectClause expression,
boolean multipleSelectItemsAllowed) {
// A subquery can have multiple select items if it is
// - used as a "root" object in the top-level FROM clause
// - defined in a UNION clause
// - used in an IN expression
// If the flag is false, then the SELECT clause is from a subquery
if (!multipleSelectItemsAllowed) {
Expression parent = expression.getParent();
multipleSelectItemsAllowed = isOwnedByFromClause (parent) ||
isOwnedByUnionClause (parent) ||
isOwnedByInExpression(parent);
}
super.validateAbstractSelectClause(expression, multipleSelectItemsAllowed);
}
@Override
public void visit(AsOfClause expression) {
}
@Override
public void visit(CastExpression expression) {
// Wrong JPA version
if (!isEclipseLink()) {
addProblem(expression, CastExpression_InvalidJPAVersion);
}
else {
validateAbstractSingleEncapsulatedExpression(expression, castExpressionHelper());
// Database type
if (expression.hasExpression() || expression.hasAs()) {
// Missing database type
if (!expression.hasDatabaseType()) {
int startPosition = position(expression) +
4 /* CAST */ +
(expression.hasLeftParenthesis() ? 1 : 0) +
length(expression.getExpression()) +
(expression.hasSpaceAfterExpression() ? 1 : 0) +
(expression.hasAs() ? 2 : 0) +
(expression.hasSpaceAfterAs() ? 1 : 0);
addProblem(expression, startPosition, CastExpression_MissingDatabaseType);
}
// Validate database type
else {
expression.getDatabaseType().accept(this);
}
}
}
}
@Override
public void visit(ConnectByClause expression) {
// TODO: 2.5
}
@Override
public void visit(DatabaseType expression) {
validateAbstractDoubleEncapsulatedExpression(expression, databaseTypeHelper());
}
@Override
public void visit(ExtractExpression expression) {
// Wrong JPA version
if (!isEclipseLink()) {
addProblem(expression, ExtractExpression_InvalidJPAVersion);
}
else {
validateAbstractSingleEncapsulatedExpression(expression, extractExpressionHelper());
// Missing date part
if (expression.hasLeftParenthesis() && !expression.hasDatePart()) {
int startPosition = position(expression) +
7 /* EXTRACT */ +
(expression.hasLeftParenthesis() ? 1 : 0);
addProblem(expression, startPosition, ExtractExpression_MissingDatePart);
}
}
}
@Override
public void visit(HierarchicalQueryClause expression) {
// TODO: 2.5
}
@Override
public void visit(OrderSiblingsByClause expression) {
// TODO
}
@Override
public void visit(RegexpExpression expression) {
// Wrong JPA version
if (!isEclipseLink()) {
addProblem(expression, RegexpExpression_InvalidJPAVersion);
}
else {
// Missing string expression
if (!expression.hasStringExpression()) {
int startPosition = position(expression);
int endPosition = startPosition;
addProblem(
expression,
startPosition,
endPosition,
RegexpExpression_MissingStringExpression
);
}
else {
Expression stringExpression = expression.getStringExpression();
// Invalid string expression
if (!isValid(stringExpression, StringExpressionBNF.ID)) {
int startPosition = position(stringExpression);
int endPosition = startPosition + length(stringExpression);
addProblem(
expression,
startPosition,
endPosition,
RegexpExpression_InvalidStringExpression
);
}
// Validate string expression
else {
stringExpression.accept(this);
}
}
// Missing pattern value
if (!expression.hasPatternValue()) {
int startPosition = position(expression) +
length(expression.getStringExpression()) +
(expression.hasSpaceAfterStringExpression() ? 1 : 0) +
6 /* REGEXP */ +
(expression.hasSpaceAfterIdentifier() ? 1 : 0);
int endPosition = startPosition;
addProblem(expression, startPosition, endPosition, RegexpExpression_MissingPatternValue);
}
else {
Expression patternValue = expression.getStringExpression();
// Invalid string expression
if (!isValid(patternValue, PatternValueBNF.ID)) {
int startPosition = position(expression) +
length(expression.getStringExpression()) +
(expression.hasSpaceAfterStringExpression() ? 1 : 0) +
6 /* REGEXP */ +
(expression.hasSpaceAfterIdentifier() ? 1 : 0);
int endPosition = startPosition + length(patternValue);
addProblem(
expression,
startPosition,
endPosition,
RegexpExpression_InvalidPatternValue
);
}
// Validate pattern value
else {
patternValue.accept(this);
}
}
}
}
@Override
public void visit(StartWithClause expression) {
// TODO: 2.5
}
@Override
public void visit(TableExpression expression) {
validateAbstractSingleEncapsulatedExpression(expression, tableExpressionHelper());
}
@Override
public void visit(TableVariableDeclaration expression) {
// Wrong JPA version
if (!isEclipseLink()) {
addProblem(expression, TableVariableDeclaration_InvalidJPAVersion);
}
else {
TableExpression tableExpression = expression.getTableExpression();
// Validate the table expression
tableExpression.accept(this);
// The identification variable is missing
if (!expression.hasIdentificationVariable()) {
int startPosition = position(expression) +
length(tableExpression) +
(expression.hasSpaceAfterTableExpression() ? 1 : 0) +
(expression.hasAs() ? 2 : 0) +
(expression.hasSpaceAfterAs() ? 1 : 0);
addProblem(expression, startPosition, TableVariableDeclaration_MissingIdentificationVariable);
}
// Validate the identification variable
else {
expression.getIdentificationVariable().accept(this);
}
}
}
@Override
public void visit(UnionClause expression) {
// Wrong JPA version
if (!isEclipseLink()) {
addProblem(expression, UnionClause_InvalidJPAVersion);
}
// Missing subquery
else if (!expression.hasQuery()) {
int startPosition = position(expression) +
expression.getIdentifier().length() +
(expression.hasSpaceAfterIdentifier() ? 1 : 0) +
(expression.hasAll() ? 3 : 0) +
(expression.hasSpaceAfterAll() ? 1 : 0);
addProblem(expression, startPosition, UnionClause_MissingExpression);
}
// Validate the subquery
else {
expression.getQuery().accept(this);
}
}
// Made static for performance reasons.
protected static class InExpressionVisitor extends AbstractEclipseLinkExpressionVisitor {
protected InExpression expression;
/**
* Default constructor.
*/
protected InExpressionVisitor() {
}
@Override
public void visit(InExpression expression) {
this.expression = expression;
}
}
// Made static final for performance reasons.
protected static final class InExpressionWithNestedArrayVisitor extends AbstractEclipseLinkExpressionVisitor {
private final EclipseLinkGrammarValidator visitor;
protected InExpressionWithNestedArrayVisitor(EclipseLinkGrammarValidator visitor) {
this.visitor = visitor;
}
/**
* Determines whether the left expression of an <code><b>IN</b></code> expression is a nested
* array when the <code><b>IN</b></code> item is a subquery.
*/
public boolean valid;
@Override
public void visit(InExpression expression) {
valid = visitor.isNestedArray(expression.getExpression());
}
@Override
public void visit(SimpleSelectClause expression) {
expression.getParent().accept(this);
}
@Override
public void visit(SimpleSelectStatement expression) {
expression.getParent().accept(this);
}
}
}