blob: d82b61fc781d4c9b28ed5103bf50f7b2d5917e0e [file] [log] [blame]
/*
* 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.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.WordParser;
import org.eclipse.persistence.jpa.jpql.parser.FunctionExpressionFactory.ParameterCount;
/**
* This expression adds support to call native database functions.
* <p>
* New to JPA 2.1.
*
* <div><b>BNF:</b> <code>func_expression ::= &lt;identifier&gt;('function_name' {, func_item}*)</code><p></p></div>
*
* @version 2.5
* @since 2.4
* @author James
*/
public final class FunctionExpression extends AbstractSingleEncapsulatedExpression {
/**
* The name of the SQL function.
*/
private String functionName;
/**
* Determines whether the comma separating the function name and the first expression.
*/
private boolean hasComma;
/**
* Determines whether a whitespace is following the comma.
*/
private boolean hasSpaceAfterComma;
/**
* The number of {@link ParameterCount parameters} a {@link FunctionExpression} can have.
*
* @since 2.4
*/
private ParameterCount parameterCount;
/**
* The unique identifier of the {@link JPQLQueryBNF} that will be used to parse the arguments of
* the function expression.
*
* @since 2.4
*/
private String parameterQueryBNFId;
/**
* Creates a new <code>FuncExpression</code>.
*
* @param parent The parent of this expression
* @param identifier The JPQL identifier
*/
public FunctionExpression(AbstractExpression parent, String identifier) {
super(parent, identifier);
}
/**
* Creates a new <code>FunctionExpression</code>.
*
* @param parent The parent of this expression
* @param identifier The JPQL identifier
* @param parameterCount The number of {@link ParameterCount parameters} a {@link
* FunctionExpression} can have
* @param parameterQueryBNFId The unique identifier of the {@link JPQLQueryBNF} that will be used
* to parse the arguments of the function expression
*/
public FunctionExpression(AbstractExpression parent,
String identifier,
ParameterCount parameterCount,
String parameterQueryBNFId) {
this(parent, identifier);
this.parameterCount = parameterCount;
this.parameterQueryBNFId = parameterQueryBNFId;
}
@Override
public void accept(ExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
protected void addOrderedEncapsulatedExpressionTo(List<Expression> children) {
children.add(buildStringExpression(functionName));
if (hasComma) {
children.add(buildStringExpression(COMMA));
}
if (hasSpaceAfterComma) {
children.add(buildStringExpression(SPACE));
}
super.addOrderedEncapsulatedExpressionTo(children);
}
@Override
public String getEncapsulatedExpressionQueryBNFId() {
return parameterQueryBNFId;
}
/**
* Returns the name of the SQL function.
*
* @return The name of the SQL function
*/
public String getFunctionName() {
return functionName;
}
/**
* Returns the number of parameters a {@link FunctionExpression} can have, which will be during
* validation.
*
* @return The number of parameters (encapsulated expressions) allowed by this expression
* @since 2.4
*/
public ParameterCount getParameterCount() {
return parameterCount;
}
@Override
public JPQLQueryBNF getQueryBNF() {
return getQueryBNF(FunctionExpressionBNF.ID);
}
/**
* Returns the name of the SQL function.
*
* @return The name of the SQL function
*/
public String getUnquotedFunctionName() {
return ExpressionTools.unquote(functionName);
}
/**
* Determines whether the comma was parsed after the function name.
*
* @return <code>true</code> if a comma was parsed after the function name and the first
* expression; <code>false</code> otherwise
*/
public boolean hasComma() {
return hasComma;
}
@Override
public boolean hasEncapsulatedExpression() {
return hasFunctionName() || hasComma || super.hasEncapsulatedExpression();
}
/**
* Determines whether the function name was parsed.
*
* @return <code>true</code> if the function name was parsed; <code>false</code> otherwise
*/
public boolean hasFunctionName() {
return ExpressionTools.stringIsNotEmpty(functionName);
}
/**
* Determines whether a whitespace was parsed after the comma.
*
* @return <code>true</code> if there was a whitespace after the comma; <code>false</code> otherwise
*/
public boolean hasSpaceAfterComma() {
return hasSpaceAfterComma;
}
@Override
protected void parseEncapsulatedExpression(WordParser wordParser,
int whitespaceCount,
boolean tolerant) {
int count = 0;
// Parse the function name outside of a CollectionExpression so we can retrieve it
// with getFunctionName()
if (wordParser.startsWith(SINGLE_QUOTE)) {
functionName = wordParser.word();
wordParser.moveForward(functionName);
count = wordParser.skipLeadingWhitespace();
}
else {
functionName = ExpressionTools.EMPTY_STRING;
}
hasComma = wordParser.startsWith(COMMA);
if (hasComma) {
wordParser.moveForward(1);
count = wordParser.skipLeadingWhitespace();
hasSpaceAfterComma = count > 0;
}
// Parse the arguments
if (!wordParser.isTail() &&
wordParser.character() != RIGHT_PARENTHESIS) {
super.parseEncapsulatedExpression(wordParser, whitespaceCount, tolerant);
}
// A space was parsed but no expression, let the space belong to the parent
if (count > 0 &&
!hasExpression() &&
!wordParser.isTail() &&
wordParser.character() != RIGHT_PARENTHESIS) {
hasSpaceAfterComma = false;
wordParser.moveBackward(count);
}
}
@Override
protected void toParsedTextEncapsulatedExpression(StringBuilder writer, boolean actual) {
writer.append(functionName);
if (hasComma) {
writer.append(COMMA);
}
if (hasSpaceAfterComma) {
writer.append(SPACE);
}
super.toParsedTextEncapsulatedExpression(writer, actual);
}
}