org.eclipse.persistence.jpa.jpql.parser.InExpression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* Copyright (c) 2006, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation
//
package org.eclipse.persistence.jpa.jpql.parser;
import java.util.Collection;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.WordParser;
/**
* The state field path expression must have a string, numeric, or enum value. The literal and/or
* input parameter values must be like the same abstract schema type of the state field path
* expression in type.
*
* The results of the subquery must be like the same abstract schema type of the state field path
* expression in type.
*
* There must be at least one element in the comma separated list that defines the set of values for
* the IN expression. If the value of a state field path expression in an IN or
* NOT IN expression is NULL or unknown, the value of the expression is unknown.
*
* JPA 1.0:
*
BNF: in_expression ::= state_field_path_expression [NOT] IN(in_item {, in_item}* | subquery)
*
* JPA 2.0:
* BNF: in_expression ::= {state_field_path_expression | type_discriminator} [NOT] IN { ( in_item {, in_item}* ) | (subquery) | collection_valued_input_parameter }
*
* EclipseLink 2.1:
*
BNF: in_item ::= literal | single_valued_input_parameter | scalar_expression
*
* EclipseLink 2.5:
*
BNF: in_expression ::= { in_expression_expression | nested_array_expression } [NOT] IN { ( in_item {, in_item}* ) | (subquery) | ( nested_array_item {, nested_array_item}+ ) | collection_valued_input_parameter }
*
* in_expression_expression ::= { state_field_path_expression | type_discriminator |
* single_valued_input_parameter | identification_variable |
* scalar_expression }
*
* nested_array_expression ::= ( in_expression_expression {, in_expression_expression}+ )
*
* nested_array_item ::= ( in_item {, in_item}+ )
*
* Example: SELECT c FROM Customer c WHERE c.home.city IN :city
*
* Example: SELECT p FROM Project p WHERE TYPE(p) IN(LargeProject, SmallProject)
*
* @version 2.5.1
* @since 2.3
* @author Pascal Filion
*/
public final class InExpression extends AbstractExpression {
/**
* The expression before the 'IN' identifier used for identification.
*/
private AbstractExpression expression;
/**
* Flag used to determine if the closing parenthesis is present in the query.
*/
private boolean hasLeftParenthesis;
/**
* Flag used to determine if the opening parenthesis is present in the query.
*/
private boolean hasRightParenthesis;
/**
* Flag used to determine if a space was parsed after IN
if the left parenthesis was
* not parsed.
*/
private boolean hasSpaceAfterIn;
/**
* The actual IN identifier found in the string representation of the JPQL query.
*/
private String inIdentifier;
/**
* The expression within parenthesis, which can be one or many expressions.
*/
private AbstractExpression inItems;
/**
* The actual NOT identifier found in the string representation of the JPQL query.
*/
private String notIdentifier;
/**
* Determines whether what was parsed after the IN
identifier is a single input parameter.
*/
private Boolean singleInputParameter;
/**
* Creates a new InExpression
.
*
* @param parent The parent of this expression
* @param expression The state field path expression that was parsed prior of parsing this
* expression
*/
public InExpression(AbstractExpression parent, AbstractExpression expression) {
super(parent, IN);
if (expression != null) {
this.expression = expression;
this.expression.setParent(this);
}
}
@Override
public void accept(ExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public void acceptChildren(ExpressionVisitor visitor) {
getExpression().accept(visitor);
getInItems().accept(visitor);
}
@Override
protected void addChildrenTo(Collection children) {
children.add(getExpression());
children.add(getInItems());
}
@Override
protected void addOrderedChildrenTo(List children) {
// State field path expression or type discriminator
if (hasExpression()) {
children.add(expression);
children.add(buildStringExpression(SPACE));
}
// 'NOT'
if (notIdentifier != null) {
children.add(buildStringExpression(NOT));
children.add(buildStringExpression(SPACE));
}
// 'IN'
children.add(buildStringExpression(IN));
// '('
if (hasLeftParenthesis) {
children.add(buildStringExpression(LEFT_PARENTHESIS));
}
else if (hasSpaceAfterIn) {
children.add(buildStringExpression(SPACE));
}
// In items
if (hasInItems()) {
children.add(inItems);
}
// ')'
if (hasRightParenthesis) {
children.add(buildStringExpression(RIGHT_PARENTHESIS));
}
}
@Override
public JPQLQueryBNF findQueryBNF(Expression expression) {
if (this.expression.isAncestor(expression)) {
return getQueryBNF(InExpressionExpressionBNF.ID);
}
if (inItems.isAncestor(expression)) {
return getQueryBNF(InExpressionItemBNF.ID);
}
return super.findQueryBNF(expression);
}
/**
* Returns the actual IN found in the string representation of the JPQL query, which has
* the actual case that was used.
*
* @return The IN identifier that was actually parsed
*/
public String getActualInIdentifier() {
return inIdentifier;
}
/**
* Returns the actual NOT found in the string representation of the JPQL query, which has
* the actual case that was used.
*
* @return The NOT identifier that was actually parsed, or an empty string if it was not parsed
*/
public String getActualNotIdentifier() {
return (notIdentifier != null) ? notIdentifier : ExpressionTools.EMPTY_STRING;
}
/**
* Returns the {@link Expression} that represents the state field path expression or type
* discriminator.
*
* @return The expression that was parsed representing the state field path expression or the
* type discriminator
*/
public Expression getExpression() {
if (expression == null) {
expression = buildNullExpression();
}
return expression;
}
/**
* Returns the unique identifier of the query BNF that describes the expression being tested by
* the IN
expression.
*
* @return {@link InExpressionExpressionBNF#ID}
*/
public String getExpressionExpressionQueryBNFId() {
return InExpressionExpressionBNF.ID;
}
/**
* Returns the unique identifier of the query BNF that describes the items being tested against.
*
* @return {@link InExpressionItemBNF#ID}
*/
public String getExpressionItemQueryBNFId() {
return InExpressionItemBNF.ID;
}
/**
* Returns the identifier for this expression.
*
* @return Either IS IN or IN
*/
public String getIdentifier() {
return (notIdentifier != null) ? NOT_IN : IN;
}
/**
* Returns the {@link Expression} that represents the list if items.
*
* @return The expression that was parsed representing the list of items
*/
public Expression getInItems() {
if (inItems == null) {
inItems = buildNullExpression();
}
return inItems;
}
@Override
public JPQLQueryBNF getQueryBNF() {
return getQueryBNF(InExpressionBNF.ID);
}
/**
* Determines whether the state field path expression or type discriminator was parsed.
*
* @return true
if the state field path expression or type discriminator was parsed;
* false
if it was not parsed
*/
public boolean hasExpression() {
return expression != null &&
!expression.isNull();
}
/**
* Determines whether the list of items was parsed.
*
* @return true
if at least one item was parsed; false
otherwise
*/
public boolean hasInItems() {
return inItems != null &&
!inItems.isNull();
}
/**
* Determines whether the open parenthesis was parsed or not.
*
* @return true
if the open parenthesis was present in the string version of the
* query; false
otherwise
*/
public boolean hasLeftParenthesis() {
return hasLeftParenthesis;
}
/**
* Determines whether the identifier NOT was parsed.
*
* @return true
if the identifier NOT was parsed; false
otherwise
*/
public boolean hasNot() {
return notIdentifier != null;
}
/**
* Determines whether the close parenthesis was parsed or not.
*
* @return true
if the close parenthesis was present in the string version of the
* query; false
otherwise
*/
public boolean hasRightParenthesis() {
return hasRightParenthesis;
}
/**
* Determines whether there was a whitespace after the IN
identifier if the left
* parenthesis was not parsed.
*
* @return true
if a whitespace was parsed after IN
in the string
* version of the query; false
otherwise
* @since 2.4
*/
public boolean hasSpaceAfterIn() {
return hasSpaceAfterIn;
}
@Override
protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) {
return word.equalsIgnoreCase(AND) ||
word.equalsIgnoreCase(OR) ||
super.isParsingComplete(wordParser, word, expression);
}
/**
* Determines whether what was parsed after the IN
identifier is a single input
* parameter:
* BNF: in_expression ::= {state_field_path_expression | type_discriminator} [NOT] IN collection_valued_input_parameter
*
* @return true
if what is following the IN
identifier is a single
* input parameter (without the left or right parenthesis); false
otherwise
* @since 2.4
*/
public boolean isSingleInputParameter() {
if (singleInputParameter == null) {
if (hasLeftParenthesis || hasRightParenthesis) {
singleInputParameter = Boolean.FALSE;
}
else {
WordParser wordParser = new WordParser(getInItems().toActualText());
String word = wordParser.word();
wordParser.moveForward(word);
singleInputParameter = (!word.isEmpty()) &&
ExpressionTools.isParameter(word.charAt(0)) &&
wordParser.isTail();
}
}
return singleInputParameter;
}
@Override
protected void parse(WordParser wordParser, boolean tolerant) {
// Parse 'NOT'
if (wordParser.startsWithIgnoreCase('N')) {
notIdentifier = wordParser.moveForward(NOT);
wordParser.skipLeadingWhitespace();
}
// Parse 'IN'
inIdentifier = wordParser.moveForward(IN);
int count = wordParser.skipLeadingWhitespace();
hasSpaceAfterIn = (count > 0);
// Parse '('
hasLeftParenthesis = wordParser.startsWith(LEFT_PARENTHESIS);
if (hasLeftParenthesis) {
wordParser.moveForward(1);
count = wordParser.skipLeadingWhitespace();
}
// Parse the items
inItems = parse(wordParser, InExpressionItemBNF.ID, tolerant);
if (inIdentifier != null) {
count = wordParser.skipLeadingWhitespace();
}
// Parse ')'
hasRightParenthesis = wordParser.startsWith(RIGHT_PARENTHESIS);
if (hasRightParenthesis) {
// Temporarily change the state so isSingleInputParameter() return the right info
hasRightParenthesis = false;
// If it's a single input parameter that is not encapsulated by parenthesis, then
// we'll ignore the right parenthesis, it could be part of an encapsulated subquery,
// example: ... WHERE (SELECT e FROM Employee e WHERE e.name IN :input_1) = :input_2
if (hasLeftParenthesis || !isSingleInputParameter()) {
hasRightParenthesis = true;
wordParser.moveForward(1);
}
}
}
@Override
protected void toParsedText(StringBuilder writer, boolean actual) {
// State field path expression or type discriminator
if (hasExpression()) {
expression.toParsedText(writer, actual);
writer.append(SPACE);
}
// 'NOT'
if (notIdentifier != null) {
writer.append(actual ? notIdentifier : NOT);
writer.append(SPACE);
}
// 'IN'
writer.append(actual ? inIdentifier : IN);
// '('
if (hasLeftParenthesis) {
writer.append(LEFT_PARENTHESIS);
}
else if (hasSpaceAfterIn) {
writer.append(SPACE);
}
// IN items
if (hasInItems()) {
inItems.toParsedText(writer, actual);
}
// ')'
if (hasRightParenthesis) {
writer.append(RIGHT_PARENTHESIS);
}
}
}