blob: 26c76922c6fa5333767b1f1a14c8b1c1931077dc [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.parser;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.WordParser;
/**
* This is the update clause of the update statement.
* <p>
* An <b>UPDATE</b> statement provides bulk operations over sets of entities of a single entity
* class (together with its subclasses, if any). Only one entity abstract schema type may be
* specified in the <b>UPDATE</b> clause.
*
* <div><b>BNF:</b> <code>update_clause ::= UPDATE abstract_schema_name [[AS] identification_variable] SET update_item {, update_item}*</code></div>
*
* @see UpdateStatement
* @see UpdateItem
*
* @version 2.5
* @since 2.3
* @author Pascal Filion
*/
public final class UpdateClause extends AbstractExpression {
/**
* Determines whether a whitespace was parsed after the abstract schema name declaration.
*/
private boolean hasSpaceAfterRangeVariableDeclaration;
/**
* Determines whether a whitespace was parsed after <b>SET</b>.
*/
private boolean hasSpaceAfterSet;
/**
* Determines whether a whitespace was parsed after <b>UPDATE</b>.
*/
private boolean hasSpaceAfterUpdate;
/**
* The {@link Expression} representing the range variable declaration.
*/
private AbstractExpression rangeVariableDeclaration;
/**
* The actual <b>SET</b> identifier found in the string representation of the JPQL query.
*/
private String setIdentifier;
/**
* The actual <b>UPDATE</b> identifier found in the string representation of the JPQL query.
*/
private String updateIdentifier;
/**
* The expression containing the update items.
*/
private AbstractExpression updateItems;
/**
* Creates a new <code>UpdateClause</code>.
*
* @param parent The parent of this expression
*/
public UpdateClause(AbstractExpression parent) {
super(parent, UPDATE);
}
@Override
public void accept(ExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public void acceptChildren(ExpressionVisitor visitor) {
getRangeVariableDeclaration().accept(visitor);
getUpdateItems().accept(visitor);
}
@Override
protected void addChildrenTo(Collection<Expression> children) {
children.add(getRangeVariableDeclaration());
children.add(getUpdateItems());
}
@Override
protected void addOrderedChildrenTo(List<Expression> children) {
// 'UPDATE'
children.add(buildStringExpression(UPDATE));
if (hasSpaceAfterUpdate) {
children.add(buildStringExpression(SPACE));
}
// Range variable declaration
if (rangeVariableDeclaration != null) {
children.add(rangeVariableDeclaration);
}
if (hasSpaceAfterRangeVariableDeclaration) {
children.add(buildStringExpression(SPACE));
}
// 'SET'
if (setIdentifier != null) {
children.add(buildStringExpression(SET));
}
if (hasSpaceAfterSet) {
children.add(buildStringExpression(SPACE));
}
// Update items
if (updateItems != null) {
children.add(updateItems);
}
}
/**
* Creates a new {@link CollectionExpression} that will wrap the single update item.
*
* @return The single update item represented by a temporary collection
*/
public CollectionExpression buildCollectionExpression() {
List<AbstractExpression> children = new ArrayList<>(1);
children.add((AbstractExpression) getUpdateItems());
List<Boolean> commas = new ArrayList<>(1);
commas.add(Boolean.FALSE);
List<Boolean> spaces = new ArrayList<>(1);
spaces.add(Boolean.FALSE);
return new CollectionExpression(this, children, commas, spaces, true);
}
@Override
public JPQLQueryBNF findQueryBNF(Expression expression) {
if ((rangeVariableDeclaration != null) && rangeVariableDeclaration.isAncestor(expression)) {
return getQueryBNF(RangeVariableDeclarationBNF.ID);
}
if ((updateItems != null) && updateItems.isAncestor(expression)) {
return getQueryBNF(InternalUpdateClauseBNF.ID);
}
return super.findQueryBNF(expression);
}
/**
* Returns the actual <b>SET</b> found in the string representation of the JPQL query, which has
* the actual case that was used.
*
* @return The <b>SET</b> identifier that was actually parsed, or an empty string if it was not parsed
*/
public String getActualSetIdentifier() {
return (setIdentifier != null) ? setIdentifier : ExpressionTools.EMPTY_STRING;
}
/**
* Returns the actual <b>UPDATE</b> found in the string representation of the JPQL query, which
* has the actual case that was used.
*
* @return The <b>UPDATE</b> identifier that was actually parsed
*/
public String getActualUpdateIdentifier() {
return updateIdentifier;
}
@Override
public JPQLQueryBNF getQueryBNF() {
return getQueryBNF(UpdateClauseBNF.ID);
}
/**
* Returns the {@link Expression} representing the range variable declaration.
*
* @return The expression that was parsed representing the range variable declaration
*/
public Expression getRangeVariableDeclaration() {
if (rangeVariableDeclaration == null) {
rangeVariableDeclaration = buildNullExpression();
}
return rangeVariableDeclaration;
}
/**
* Returns the {@link Expression} representing the single update item or the collection of update items.
*
* @return The expression that was parsed representing the single or multiple update items
*/
public Expression getUpdateItems() {
if (updateItems == null) {
updateItems = buildNullExpression();
}
return updateItems;
}
/**
* Determines whether the range variable declaration was parsed.
*
* @return <code>true</code> if the range variable declaration was parsed; <code>false</code> otherwise
*/
public boolean hasRangeVariableDeclaration() {
return rangeVariableDeclaration != null &&
!rangeVariableDeclaration.isNull();
}
/**
* Determines whether <b>SET</b> was parsed or not.
*
* @return <code>true</code> if <b>SET</b> was part of the query; <code>false</code> otherwise
*/
public boolean hasSet() {
return setIdentifier != null;
}
/**
* Determines whether a whitespace was found after the abstract schema name declaration.
*
* @return <code>true</code> if there was a whitespace after the abstract schema name declaration;
* <code>false</code> otherwise
*/
public boolean hasSpaceAfterRangeVariableDeclaration() {
return hasSpaceAfterRangeVariableDeclaration;
}
/**
* Determines whether a whitespace was found after <b>SET</b>.
*
* @return <code>true</code> if there was a whitespace after <b>SET</b>; <code>false</code> otherwise
*/
public boolean hasSpaceAfterSet() {
return hasSpaceAfterSet;
}
/**
* Determines whether a whitespace was found after the identifier <b>UPDATE</b>.
*
* @return <code>true</code> if there was a whitespace after the identifier <b>UPDATE</b>;
* <code>false</code> otherwise
*/
public boolean hasSpaceAfterUpdate() {
return hasSpaceAfterUpdate;
}
/**
* Determines whether the update items section of the query was parsed.
*
* @return <code>true</code> if something was parsed after <b>SET</b> even if it was a malformed
* expression; <code>false</code> if nothing was parsed
*/
public boolean hasUpdateItems() {
return updateItems != null &&
!updateItems.isNull();
}
@Override
protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) {
return word.equalsIgnoreCase(SET) ||
super.isParsingComplete(wordParser, word, expression);
}
@Override
protected void parse(WordParser wordParser, boolean tolerant) {
// Parse 'UPDATE'
updateIdentifier = wordParser.moveForward(UPDATE);
hasSpaceAfterUpdate = wordParser.skipLeadingWhitespace() > 0;
// Parse the abstract schema name
if (tolerant && !wordParser.startsWithIdentifier(SET)) {
rangeVariableDeclaration = parse(
wordParser,
RangeVariableDeclarationBNF.ID,
tolerant
);
}
else if (!tolerant) {
rangeVariableDeclaration = new RangeVariableDeclaration(this);
rangeVariableDeclaration.parse(wordParser, tolerant);
}
hasSpaceAfterRangeVariableDeclaration = wordParser.skipLeadingWhitespace() > 0;
// Parse 'SET'
if (!tolerant || wordParser.startsWithIdentifier(SET)) {
setIdentifier = wordParser.moveForward(SET);
hasSpaceAfterSet = wordParser.skipLeadingWhitespace() > 0;
}
// Parse update items
updateItems = parse(wordParser, InternalUpdateClauseBNF.ID, tolerant);
}
@Override
protected void toParsedText(StringBuilder writer, boolean actual) {
// 'UPDATE'
writer.append(actual ? updateIdentifier : UPDATE);
if (hasSpaceAfterUpdate) {
writer.append(SPACE);
}
// Range variable declaration
if (rangeVariableDeclaration != null) {
rangeVariableDeclaration.toParsedText(writer, actual);
}
if (hasSpaceAfterRangeVariableDeclaration) {
writer.append(SPACE);
}
// 'SET'
if (setIdentifier != null) {
writer.append(actual ? setIdentifier : SET);
}
if (hasSpaceAfterSet) {
writer.append(SPACE);
}
// Update items
if (updateItems != null) {
updateItems.toParsedText(writer, actual);
}
}
}