org.eclipse.persistence.jpa.jpql.parser.CastExpression 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) 2012, 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.parser;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.WordParser;
/**
* The CAST function cast value to a different type. The database type is the second parameter,
* and can be any valid database type including size and scale.
*
* BNF: expression ::= CAST(scalar_expression [AS] database_type)
*
* @see DatabaseType
*
* @version 2.5
* @since 2.4
* @author James Sutherland
*/
public final class CastExpression extends AbstractSingleEncapsulatedExpression {
/**
* The actual AS identifier found in the string representation of the JPQL query.
*/
private String asIdentifier;
/**
* The {@link Expression} representing the database type to cast to.
*/
private AbstractExpression databaseType;
/**
* Determines whether a space was parsed after the identifier AS.
*/
private boolean hasSpaceAfterAs;
/**
* Determines whether a space was parsed after the expression.
*/
private boolean hasSpaceAfterExpression;
/**
* Flag used to determine how to stop parsing based on what is being parsed.
*/
private boolean parsingDatabaseType;
/**
* This flag is used to prevent the parsing from using any {@link ExpressionFactory} before
* going through the fallback procedure. This is necessary because only a {@link DatabaseType}
* should be created, even if the database type is a JPQL identifier (eg: TIMESTAMP
).
*/
private boolean shouldParseWithFactoryFirst;
/**
* Creates a new CastExpression
.
*
* @param parent The parent of this expression
*/
public CastExpression(AbstractExpression parent) {
super(parent, CAST);
shouldParseWithFactoryFirst = true;
}
@Override
public void accept(ExpressionVisitor visitor) {
acceptUnknownVisitor(visitor);
}
@Override
protected void addOrderedEncapsulatedExpressionTo(List children) {
// Value
super.addOrderedEncapsulatedExpressionTo(children);
if (hasSpaceAfterExpression) {
children.add(buildStringExpression(SPACE));
}
// 'AS'
if (asIdentifier != null) {
children.add(buildStringExpression(AS));
}
if (hasSpaceAfterAs) {
children.add(buildStringExpression(SPACE));
}
if (hasDatabaseType()) {
children.add(databaseType);
}
}
@Override
public String getEncapsulatedExpressionQueryBNFId() {
return ScalarExpressionBNF.ID;
}
/**
* Returns the database type to cast to.
*
* @return The {@link Expression} representing the database type
*/
public Expression getDatabaseType() {
return databaseType;
}
@Override
public JPQLQueryBNF getQueryBNF() {
return getQueryBNF(CastExpressionBNF.ID);
}
/**
* Determines whether the identifier AS was part of the query.
*
* @return true
if the identifier AS was parsed; false
otherwise
*/
public boolean hasAs() {
return asIdentifier != null;
}
/**
* Determines whether the database type was parsed or not.
*
* @return true
if the database type was parsed; false
otherwise
*/
public boolean hasDatabaseType() {
return databaseType != null &&
!databaseType.isNull();
}
@Override
public boolean hasEncapsulatedExpression() {
return super.hasEncapsulatedExpression() || (asIdentifier != null) || hasDatabaseType();
}
/**
* Determines whether something was parsed after the left parenthesis and before the
* AS
identifier.
*
* @return true
the expression to be cast was parsed; false
otherwise
* @since 2.5
*/
public boolean hasScalarExpression() {
return super.hasEncapsulatedExpression();
}
/**
* Determines whether a whitespace parsed after AS.
*
* @return true
if there was a whitespace parsed after AS;
* false
otherwise
*/
public boolean hasSpaceAfterAs() {
return hasSpaceAfterAs;
}
/**
* Determines whether a whitespace was parsed after the expression.
*
* @return true
if there was a whitespace parsed after the expression;
* false
otherwise
*/
public boolean hasSpaceAfterExpression() {
return hasSpaceAfterExpression;
}
@Override
protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) {
if (parsingDatabaseType) {
return super.isParsingComplete(wordParser, word, expression);
}
ExpressionFactory factory = getQueryBNF(getEncapsulatedExpressionQueryBNFId()).getExpressionFactory(word);
// The first check is used to stop parsing the scalar expression,
// example: CAST(e.firstName NUMERIC(2, 3)) and "NUMERIC" is the current word,
// it cannot be part of the scalar expression but will be the database type
// TODO: Add support for tolerance and the scalar expression is invalid, like
// having 'x AND y', it probably should be parsed in its entirety
return (factory == null && expression != null) ||
word.equalsIgnoreCase(AS) ||
super.isParsingComplete(wordParser, word, expression);
}
@Override
protected void parseEncapsulatedExpression(WordParser wordParser,
int whitespaceCount,
boolean tolerant) {
// Parse the value
super.parseEncapsulatedExpression(wordParser, whitespaceCount, tolerant);
hasSpaceAfterExpression = wordParser.skipLeadingWhitespace() > 0;
// Parse 'AS'
if (wordParser.startsWithIdentifier(AS)) {
asIdentifier = wordParser.moveForward(AS);
hasSpaceAfterAs = wordParser.skipLeadingWhitespace() > 0;
}
// Parse the database type
if (!wordParser.isTail()) {
parsingDatabaseType = true;
if (tolerant) {
databaseType = parse(wordParser, DatabaseTypeQueryBNF.ID, tolerant);
}
else {
databaseType = new DatabaseType(this, wordParser.word());
databaseType.parse(wordParser, tolerant);
}
}
}
@Override
protected void removeEncapsulatedExpression() {
super.removeEncapsulatedExpression();
asIdentifier = null;
databaseType = null;
hasSpaceAfterAs = false;
hasSpaceAfterExpression = false;
}
@Override
protected boolean shouldParseWithFactoryFirst() {
return shouldParseWithFactoryFirst;
}
@Override
protected void toParsedTextEncapsulatedExpression(StringBuilder writer, boolean actual) {
// Value
super.toParsedTextEncapsulatedExpression(writer, actual);
if (hasSpaceAfterExpression) {
writer.append(SPACE);
}
// 'AS'
if (asIdentifier != null) {
writer.append(actual ? asIdentifier : AS);
}
if (hasSpaceAfterAs) {
writer.append(SPACE);
}
// Database type
if (databaseType != null) {
databaseType.toParsedText(writer, actual);
}
}
}