diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.jpql/src/test/java/org/eclipse/persistence/testing/tests/jpa/jpql/advanced/JUnitJPQLJakartaDataNoAliasTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.jpql/src/test/java/org/eclipse/persistence/testing/tests/jpa/jpql/advanced/JUnitJPQLJakartaDataNoAliasTest.java index 8a65a870280..d056cac9e3c 100644 --- a/jpa/eclipselink.jpa.testapps/jpa.test.jpql/src/test/java/org/eclipse/persistence/testing/tests/jpa/jpql/advanced/JUnitJPQLJakartaDataNoAliasTest.java +++ b/jpa/eclipselink.jpa.testapps/jpa.test.jpql/src/test/java/org/eclipse/persistence/testing/tests/jpa/jpql/advanced/JUnitJPQLJakartaDataNoAliasTest.java @@ -20,8 +20,11 @@ import junit.framework.Test; import junit.framework.TestSuite; import org.eclipse.persistence.queries.ReadObjectQuery; +import org.eclipse.persistence.sessions.server.ServerSession; import org.eclipse.persistence.testing.framework.jpa.junit.JUnitTestCase; +import org.eclipse.persistence.testing.models.jpa.advanced.AdvancedTableCreator; import org.eclipse.persistence.testing.models.jpa.advanced.EmployeePopulator; +import org.eclipse.persistence.testing.models.jpa.advanced.Room; import org.eclipse.persistence.testing.models.jpa.datatypes.DataTypesTableCreator; import org.eclipse.persistence.testing.models.jpa.datatypes.WrapperTypes; import org.eclipse.persistence.testing.tests.jpa.jpql.JUnitDomainObjectComparer; @@ -29,11 +32,7 @@ import java.math.BigDecimal; import java.math.BigInteger; -import java.util.List; import java.util.stream.Stream; -import org.eclipse.persistence.sessions.server.ServerSession; -import org.eclipse.persistence.testing.models.jpa.advanced.AdvancedTableCreator; -import org.eclipse.persistence.testing.models.jpa.advanced.Room; /** *

@@ -53,14 +52,14 @@ public class JUnitJPQLJakartaDataNoAliasTest extends JUnitTestCase { private static final String STRING_DATA = "A String"; private static final String STRING_DATA_LIKE_EXPRESSION = "A%"; // should match STRING_DATA - private static final Room[] ROOMS = new Room[]{ - null, // Skip array index 0 - aRoom(1, 1, 1, 1), - aRoom(2, 1, 1, 1), - aRoom(3, 1, 1, 1), - aRoom(4, 1, 1, 1) + private static final Room[] ROOMS = new Room[] { + null, // Skip array index 0 + aRoom(1, 1, 1, 1), + aRoom(2, 1, 1, 1), + aRoom(3, 1, 1, 1), + aRoom(4, 1, 1, 1) }; - private static final long ROOMS_COUNT = ROOMS.length - 1; // we ignore the first one with index 0 + private static final long ROOMS_COUNT = ROOMS.length -1; // we ignore the first one with index 0 private static int wrapperId; @@ -102,11 +101,6 @@ public static Test suite() { suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testNoAliasFromWhereAndUPPER")); suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testGeneratedSelectNoAliasFromWhere")); suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testGeneratedSelect")); - suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testUpdateQueryLengthInAssignmentAndExpression")); - suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testSelectQueryLengthInAssignmentAndExpression")); - suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testUpdateImplicitVariableInArithmeticExpression")); - suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testDeleteQueryLengthInExpressionOnLeft")); - suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testDeleteQueryLengthInExpressionOnRight")); suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("tesUpdateQueryWithThisVariable")); suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testThisVariableInPathExpressionUpdate")); suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testThisVariableInPathExpressionDelete")); @@ -265,55 +259,6 @@ public void testGeneratedSelectNoAliasFromWhere() { Assert.assertTrue("GeneratedSelectNoAliasFromWhere Test Failed", comparer.compareObjects(wrapperTypes, tlWrapperTypes)); } - public void testUpdateQueryLengthInAssignmentAndExpression() { - resetRooms(); - long numberOfChanges = getEntityManagerFactory().callInTransaction(em -> em.createQuery( - "UPDATE Room SET length = length + 1").executeUpdate()); - assertTrue("All rooms should be updated", numberOfChanges == ROOMS_COUNT); - - long numberOfRoomsWithLengthChanged = getAllRooms() - .filter(room -> room.getLength() == 2) - .count(); - assertTrue("All rooms should have increased length", numberOfRoomsWithLengthChanged == ROOMS_COUNT); - } - - public void testSelectQueryLengthInAssignmentAndExpression() { - resetRooms(); - List roomsWithIdOne = getEntityManagerFactory().callInTransaction(em -> em.createQuery( - "SELECT this FROM Room WHERE id + length = length + 1", Room.class).getResultList()); - assertTrue("Number of rooms with ID = 1", roomsWithIdOne.size() == 1); - } - - public void testUpdateImplicitVariableInArithmeticExpression() { - resetRooms(); - int numberOfChanges = getEntityManagerFactory().callInTransaction(em -> em.createQuery( - "UPDATE Room SET width = width * :widthMultiplicator WHERE id = :id") - .setParameter("widthMultiplicator", 5) - .setParameter("id", 1) - .executeUpdate()); - assertTrue("Number of rooms with ID = 1 updated", numberOfChanges == 1); - int roomWidth = findRoomById(1).getWidth(); - assertTrue("Room ID = 1 has width of ", roomWidth == 5); - } - - public void testDeleteQueryLengthInExpressionOnLeft() { - resetRooms(); - assertTrue("Number of remaining rooms", getAllRooms().count() == ROOMS_COUNT); - int numberOfChanges = getEntityManagerFactory().callInTransaction(em -> em.createQuery( - "DELETE FROM Room WHERE length = id - 1").executeUpdate()); - long allRoomsCount = getAllRooms().count(); - assertTrue("Number of rooms with ID = 1 deleted", numberOfChanges == 1); - assertTrue("Number of remaining rooms", allRoomsCount == ROOMS_COUNT - 1); - } - - public void testDeleteQueryLengthInExpressionOnRight() { - resetRooms(); - int numberOfChanges = getEntityManagerFactory().callInTransaction(em -> em.createQuery( - "DELETE FROM Room WHERE id = length + 1").executeUpdate()); - assertTrue("Number of rooms with ID = 1 deleted", numberOfChanges == 1); - assertTrue("Number of remaining rooms", getAllRooms().count() == ROOMS_COUNT - 1); - } - public void tesUpdateQueryWithThisVariable() { resetRooms(); long numberOfChanges = getEntityManagerFactory().callInTransaction(em -> em.createQuery( diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/WordParser.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/WordParser.java index 58a3343db4f..57265e47bde 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/WordParser.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/WordParser.java @@ -1,7 +1,6 @@ /* * Copyright (c) 2006, 2022 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022 IBM Corporation. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 @@ -257,7 +256,6 @@ public int length() { */ public void moveBackward(CharSequence word) { cursor -= word.length(); - wordEndPosition(); } /** diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractEncapsulatedExpression.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractEncapsulatedExpression.java index f4126450b6c..c62cc48b58b 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractEncapsulatedExpression.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractEncapsulatedExpression.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2006, 2023 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 @@ -307,19 +306,4 @@ else if (hasSpaceAfterIdentifier) { * like content assist */ protected abstract void toParsedTextEncapsulatedExpression(StringBuilder writer, boolean actual); - - /** - * Should be reverted if Jakarta Data mode is enabled and the expression doesn't contain parenthesis. - * For example, the query {@code delete from Box where length = 1} contains function name {@code length}. - * The function {@code length} expects parameters but there are not parameters in the query. - * Then {@code length} is not a function but a state field with an implicit identification variable this, ie. - * the query is equivalent to {@code delete from Box where this.length = 1}. - * - * @return Should be reverted and parsed again as a different expression or should be accepted as parsed expression - */ - @Override - protected boolean isInvalid() { - return getRoot().isJakartaData() && !hasLeftParenthesis(); - } - } diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractExpression.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractExpression.java index a6e2e034a04..bb77a0becc1 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractExpression.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractExpression.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2006, 2024 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 @@ -16,7 +15,6 @@ // package org.eclipse.persistence.jpa.jpql.parser; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.AccessController; @@ -518,22 +516,6 @@ public final JPQLExpression getRoot() { return (parent == null) ? (JPQLExpression) this : parent.getRoot(); } - /** - * Returns closest nested expression that encapsulates this expression, - * or the root expression if not inside a nested expression. - * - * @return Parent expression - */ - public final ParentExpression getParentExpression() { - if (this instanceof ParentExpression parentExpression) { - return parentExpression; - } else if (parent == null) { - return null; - } else { - return parent.getParentExpression(); - } - } - /** * Returns the encapsulated text of this {@link AbstractExpression}, which can be used in various * ways, it can be a keyword, a literal, etc. @@ -787,9 +769,6 @@ tolerant && isParsingComplete(wordParser, word, expression)) { if (factory != null) { child = factory.buildExpression(this, wordParser, word, queryBNF, expression, tolerant); - // if an invalid expression came from the factory, ignore it and try fallback - child = revertExpressionIfInvalid(child, wordParser, word); - if (child != null) { // The new expression is a child of the previous expression, @@ -1009,14 +988,6 @@ else if (currentInfo != null) { ); } - static AbstractExpression revertExpressionIfInvalid(AbstractExpression expression, WordParser wordParser, String word) { - if (expression != null && expression.isInvalid()) { - wordParser.moveBackward(word); - return null; - } - return expression; - } - /** * Right away parses the text by retrieving the {@link ExpressionFactory} for the first word that * is extracted from {@link WordParser} at the current location. @@ -1162,17 +1133,6 @@ public String toParsedText() { */ protected abstract void toParsedText(StringBuilder writer, boolean actual); - /** - * Whether this expression is not valid and should be discarded. If it returns true, - * the parser will be reverted to the state before this expression was parsed - * and it will attempt to parse a different expression. - * - * @return True if this expression is invalid and should be discarded, otherwise false. By default returns false, should be overriden if expression should be reverted. - */ - protected boolean isInvalid() { - return false; - } - @Override public final String toString() { // toString() should only be called during debugging, thus the cached parsed text diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractFromClause.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractFromClause.java index de7cc021dae..5a1af5a8ef9 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractFromClause.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractFromClause.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2006, 2024 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 @@ -344,7 +343,7 @@ else if (hierarchicalQueryClause == null) { identificationVariableDeclaration.hasRangeVariableDeclaration() && identificationVariableDeclaration.getRangeVariableDeclaration() instanceof RangeVariableDeclaration rangeVariableDeclaration && rangeVariableDeclaration.hasIdentificationVariable() && rangeVariableDeclaration.getIdentificationVariable() instanceof IdentificationVariable identificationVariable && Expression.THIS.equals(identificationVariable.getText())) { - this.getParentExpression().setGenerateThisPrefix(true); + this.getRoot().setGenerateThisPrefix(true); } } diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractLiteralExpressionFactory.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractLiteralExpressionFactory.java index d98729261b2..5044ec99180 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractLiteralExpressionFactory.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/AbstractLiteralExpressionFactory.java @@ -1,7 +1,6 @@ /* * Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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, @@ -65,64 +64,67 @@ protected AbstractExpression buildExpression(AbstractExpression parent, WordParser wordParser, String word, JPQLQueryBNF queryBNF, - final AbstractExpression expression, + AbstractExpression expression, boolean tolerant) { switch (wordParser.getWordType()) { case NUMERIC_LITERAL: { - NumericLiteral numericLiteral = new NumericLiteral(parent, word); - numericLiteral.parse(wordParser, tolerant); - return numericLiteral; + expression = new NumericLiteral(parent, word); + expression.parse(wordParser, tolerant); + return expression; } case STRING_LITERAL: { - StringLiteral stringLiteral = new StringLiteral(parent, word); - stringLiteral.parse(wordParser, tolerant); - return stringLiteral; + expression = new StringLiteral(parent, word); + expression.parse(wordParser, tolerant); + return expression; } case INPUT_PARAMETER: { - InputParameter inputParameter = new InputParameter(parent, word); - inputParameter.parse(wordParser, tolerant); - return inputParameter; + expression = new InputParameter(parent, word); + expression.parse(wordParser, tolerant); + return expression; } + } - default: { - // Path expression - if (word.indexOf(AbstractExpression.DOT) > -1) { - char character = word.charAt(0); - AbstractPathExpression pathExpression; - - if ((expression != null) && (character == AbstractExpression.DOT)) { - if (isCollection()) { - pathExpression = new CollectionValuedPathExpression(parent, expression, word); - } else { - pathExpression = new StateFieldPathExpression(parent, expression, word); - } - } else { - if (isCollection()) { - pathExpression = new CollectionValuedPathExpression(parent, word); - } else { - pathExpression = new StateFieldPathExpression(parent, word); - } - } - - pathExpression.parse(wordParser, tolerant); - return pathExpression; + // Path expression + if (word.indexOf(AbstractExpression.DOT) > -1) { + char character = word.charAt(0); + + if ((expression != null) && (character == AbstractExpression.DOT)) { + if (isCollection()) { + expression = new CollectionValuedPathExpression(parent, expression, word); + } + else { + expression = new StateFieldPathExpression(parent, expression, word); } + } + else { + if (isCollection()) { + expression = new CollectionValuedPathExpression(parent, word); + } + else { + expression = new StateFieldPathExpression(parent, word); + } + } - // Checks for invalid JPQL queries + expression.parse(wordParser, tolerant); + return expression; + } - if (tolerant && getExpressionRegistry().isIdentifier(word)) { - // Before creating the expression, check to make sure it's not a function: 'identifier(' - AbstractExpression identifierExpression; - identifierExpression = getIdentifierExpression(parent, wordParser, word, queryBNF, expression, tolerant); - if (identifierExpression != null) { - return new BadExpression(parent, identifierExpression); - } - } + // Checks for invalid JPQL queries + ExpressionRegistry registry = getExpressionRegistry(); + if (tolerant && registry.isIdentifier(word)) { + ExpressionFactory factory = registry.expressionFactoryForIdentifier(word); + // TODO: Before creating the expression, check to make sure it's not a function: 'identifier(' + if (factory != null) { + expression = factory.buildExpression(parent, wordParser, word, queryBNF, expression, tolerant); + + if (expression != null) { + return new BadExpression(parent, expression); + } } } @@ -136,16 +138,4 @@ protected AbstractExpression buildExpression(AbstractExpression parent, protected boolean isCollection() { return false; } - - private AbstractExpression getIdentifierExpression(AbstractExpression parent, WordParser wordParser, String word, JPQLQueryBNF queryBNF, AbstractExpression expression, boolean tolerant) { - ExpressionFactory factory = getExpressionRegistry().expressionFactoryForIdentifier(word); - // TODO: Before creating the expression, check to make sure it's not a function: 'identifier(' - if (factory != null) { - final AbstractExpression identifierExpression = factory.buildExpression(parent, wordParser, word, queryBNF, expression, tolerant); - - // if an invalid expression came from the factory, e.g. a function without expected arguments, ignore it - return AbstractExpression.revertExpressionIfInvalid(identifierExpression, wordParser, word); - } - return null; - } } diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/ExpressionFactory.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/ExpressionFactory.java index 3eb7c089145..ca6a990605a 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/ExpressionFactory.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/ExpressionFactory.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2006, 2024 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/IdentificationVariable.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/IdentificationVariable.java index 822da4d76f0..84ab752f195 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/IdentificationVariable.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/IdentificationVariable.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2006, 2024 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 @@ -95,7 +94,7 @@ public final class IdentificationVariable extends AbstractExpression { */ public IdentificationVariable(AbstractExpression parent, String identificationVariable) { super(parent, identificationVariable); - if (!Expression.THIS.equalsIgnoreCase(identificationVariable) && getParentExpression().isGenerateThisPrefix()) { + if (!Expression.THIS.equalsIgnoreCase(identificationVariable) && getRoot().isGenerateThisPrefix()) { this.setVirtualIdentificationVariable(Expression.THIS); } } @@ -117,7 +116,7 @@ public IdentificationVariable(AbstractExpression parent, String identificationVa public void accept(ExpressionVisitor visitor) { //In "this" (Jakarta Data) generation mode pass for a validation stateFieldPathExpression //generated by this.setVirtualIdentificationVariable(Expression.THIS); in constructor above - if (getParentExpression().isGenerateThisPrefix() && isVirtual() && stateFieldPathExpression != null) { + if (getRoot().isGenerateThisPrefix() && isVirtual() && stateFieldPathExpression != null) { visitor.visit(getStateFieldPathExpression()); } else { visitor.visit(this); diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/JPQLExpression.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/JPQLExpression.java index e31a0e75e15..0bcd22f92d4 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/JPQLExpression.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/JPQLExpression.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2006, 2024 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 @@ -42,7 +41,7 @@ * @author Pascal Filion */ @SuppressWarnings("nls") -public final class JPQLExpression extends AbstractExpression implements ParentExpression { +public final class JPQLExpression extends AbstractExpression { /** * The JPQL grammar that defines how to parse a JPQL query. @@ -89,6 +88,9 @@ public final class JPQLExpression extends AbstractExpression implements ParentEx */ private boolean jakartaData = false; + /** + * Automatically add missing "this" prefixes into where field variables if it doesn't exist. + */ private boolean generateThisPrefix = false; /** @@ -256,12 +258,10 @@ public JPQLQueryBNF getQueryBNF() { return getQueryBNF(queryBNFId); } - @Override public boolean isGenerateThisPrefix() { return generateThisPrefix; } - @Override public void setGenerateThisPrefix(boolean generateThisPrefix) { this.generateThisPrefix = generateThisPrefix; } diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/LengthExpressionFactory.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/LengthExpressionFactory.java index f989be011b9..b6aa05a11ba 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/LengthExpressionFactory.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/LengthExpressionFactory.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 @@ -54,5 +53,4 @@ protected AbstractExpression buildExpression(AbstractExpression parent, expression.parse(wordParser, tolerant); return expression; } - } diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/ParentExpression.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/ParentExpression.java deleted file mode 100644 index a036090fa8d..00000000000 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/ParentExpression.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2024 Contributors to the Eclipse Foundation. - * - * 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: -// 21/07/2024: Ondro Mihalyi - implicit variable in sub-expressions -package org.eclipse.persistence.jpa.jpql.parser; - -public interface ParentExpression extends Expression { - - /** - * Whether should automatically add missing "this" prefix into where field variables if it doesn't exist. - */ - boolean isGenerateThisPrefix(); - - void setGenerateThisPrefix(boolean generateThisPrefix); - -} diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/RangeVariableDeclaration.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/RangeVariableDeclaration.java index 6d74dd38351..8dfff4f2e4a 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/RangeVariableDeclaration.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/RangeVariableDeclaration.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2006, 2024 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 @@ -276,16 +275,19 @@ protected void parse(WordParser wordParser, boolean tolerant) { hasSpaceAfterAs = wordParser.skipLeadingWhitespace() > 0; } - if (tolerant) { - identificationVariable = parse(wordParser, IdentificationVariableBNF.ID, tolerant); - if (identificationVariable == null && this.getRoot().isJakartaData()) { - addMissingAlias(Expression.THIS); + // Special case when parsing the range variable declaration of an UPDATE clause that does + // not have an identification variable, e.g. "UPDATE DateTime SET date = CURRENT_DATE" + if (!wordParser.startsWithIdentifier(SET)) { + if (tolerant) { + identificationVariable = parse(wordParser, IdentificationVariableBNF.ID, tolerant); + if (identificationVariable == null && this.getRoot().isJakartaData()) { + addMissingAlias(Expression.THIS); + } + } + else { + identificationVariable = new IdentificationVariable(this, wordParser.word()); + identificationVariable.parse(wordParser, tolerant); } - } else if (!wordParser.startsWithIdentifier(SET)) { - // We need to avoid the special valid case when parsing the range variable declaration of an UPDATE clause that does - // not have an identification variable, e.g. "UPDATE DateTime SET date = CURRENT_DATE" - identificationVariable = new IdentificationVariable(this, wordParser.word()); - identificationVariable.parse(wordParser, tolerant); } } @@ -363,27 +365,11 @@ protected void toParsedText(StringBuilder writer, boolean actual) { * @param aliasName Entity alias. */ private void addMissingAlias(String aliasName) { - if (isMissingAliasInSelectFromClause() - || isMissingAliasInUpdateClause() - || isMissingAliasInDeleteFromClause()) { + if (this.getParent() instanceof IdentificationVariableDeclaration identificationVariableDeclaration && + identificationVariableDeclaration.getParent() instanceof FromClause && + this.getIdentificationVariable() instanceof NullExpression) { this.setVirtualIdentificationVariable(aliasName); - this.getParentExpression().setGenerateThisPrefix(true); + this.getRoot().setGenerateThisPrefix(true); } } - - private boolean isMissingAliasInSelectFromClause() { - return this.getParent() instanceof IdentificationVariableDeclaration identificationVariableDeclaration - && identificationVariableDeclaration.getParent() instanceof FromClause - && this.getIdentificationVariable() instanceof NullExpression; - } - - private boolean isMissingAliasInUpdateClause() { - return this.getParent() instanceof UpdateClause - && this.getIdentificationVariable() instanceof NullExpression; - } - - private boolean isMissingAliasInDeleteFromClause() { - return this.getParent() instanceof DeleteClause deleteClause - && this.getIdentificationVariable() instanceof NullExpression; - } } diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/SubExpression.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/SubExpression.java index 037043aaf50..ba5024b6d38 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/SubExpression.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/SubExpression.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 @@ -27,15 +26,13 @@ * @since 2.3 * @author Pascal Filion */ -public final class SubExpression extends AbstractSingleEncapsulatedExpression implements ParentExpression { +public final class SubExpression extends AbstractSingleEncapsulatedExpression { /** * The {@link JPQLQueryBNF} coming from the parent that is used to parse the next portion of the query. */ private JPQLQueryBNF queryBNF; - private boolean generateThisPrefix = false; - /** * Creates a new SubExpression. * @@ -47,16 +44,6 @@ public SubExpression(AbstractExpression parent, JPQLQueryBNF queryBNF) { this.queryBNF = queryBNF; } - @Override - public boolean isGenerateThisPrefix() { - return generateThisPrefix; - } - - @Override - public void setGenerateThisPrefix(boolean generateThisPrefix) { - this.generateThisPrefix = generateThisPrefix; - } - @Override public void accept(ExpressionVisitor visitor) { visitor.visit(this); diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/jpql/query/Order.java b/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/jpql/query/Order.java index 1c47c5ad07d..aca03e901b7 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/jpql/query/Order.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/jpql/query/Order.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 @@ -42,5 +41,4 @@ public class Order { private double totalPrice; @Id private int id; private String number; - private int length; } diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/parser/JPQLExpressionTestJakartaData.java b/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/parser/JPQLExpressionTestJakartaData.java index f39540dbd10..f18607f2430 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/parser/JPQLExpressionTestJakartaData.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/parser/JPQLExpressionTestJakartaData.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 @@ -16,19 +15,6 @@ // package org.eclipse.persistence.jpa.tests.jpql.parser; -import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLParserTester.equal; -import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLParserTester.from; -import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLParserTester.numeric; -import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLParserTester.path; -import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLParserTester.select; -import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLParserTester.selectStatement; -import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLParserTester.set; -import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLParserTester.update; -import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLParserTester.updateStatement; -import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLParserTester.variable; -import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLParserTester.virtualVariable; -import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLParserTester.where; - import org.eclipse.persistence.jpa.jpql.parser.Expression; import org.eclipse.persistence.jpa.jpql.parser.FromClause; import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariableDeclaration; @@ -121,73 +107,25 @@ public void testGeneratedSelect() { assertEquals(expectedJPQLQuery, jpqlExpression.toActualText()); } - @Test - public void testFunctionNameAsImplicitStateField() { - - String inputJPQLQuery = "SELECT this FROM Order WHERE length = 1"; - - SelectStatementTester selectStatement = selectStatement( - select(variable("this")), - from("Order", "{this}"), - where(equal(virtualVariable("this", "length"), numeric(1))) - ); - - testJakartaDataQuery(inputJPQLQuery, selectStatement); - } - - @Test - public void testFunctionNameAsImplicitStateFieldInNumericExpression() { - - String inputJPQLQuery = "SELECT this FROM Order WHERE id = length + 1"; - - SelectStatementTester selectStatement = selectStatement( - select(variable("this")), - from("Order", "{this}"), - where(equal( - virtualVariable("this", "id"), - virtualVariable("this", "length").add(numeric(1)) - )) - ); - - testJakartaDataQuery(inputJPQLQuery, selectStatement); - } - - @Test - public void testUpdateFunctionNameAsImplicitStateFieldInNumericExpression() { - - String inputJPQLQuery = "UPDATE Order SET length = length + 1"; - - UpdateStatementTester selectStatement = updateStatement( - update( - "Order", - set(path("{order}.length"), - virtualVariable("order", "length") - .add(numeric(1)) - )) - ); - - testJakartaDataQuery(inputJPQLQuery, selectStatement); - } - private JPQLExpression checkAliasFrom(String actualQuery, String expectedAlias) { JPQLExpression jpqlExpression = JPQLQueryBuilder.buildQuery(actualQuery, JPQLGrammar3_2.instance(), JPQLStatementBNF.ID, true, true); - SelectStatement selectStatement = (SelectStatement) jpqlExpression.getQueryStatement(); - FromClause fromClause = (FromClause) selectStatement.getFromClause(); - IdentificationVariableDeclaration identificationVariableDeclaration = (IdentificationVariableDeclaration) fromClause.getDeclaration(); - RangeVariableDeclaration rangeVariableDeclaration = (RangeVariableDeclaration) identificationVariableDeclaration.getRangeVariableDeclaration(); - IdentificationVariable identificationVariable = (IdentificationVariable) rangeVariableDeclaration.getIdentificationVariable(); + SelectStatement selectStatement = (SelectStatement)jpqlExpression.getQueryStatement(); + FromClause fromClause = (FromClause)selectStatement.getFromClause(); + IdentificationVariableDeclaration identificationVariableDeclaration = (IdentificationVariableDeclaration)fromClause.getDeclaration(); + RangeVariableDeclaration rangeVariableDeclaration = (RangeVariableDeclaration)identificationVariableDeclaration.getRangeVariableDeclaration(); + IdentificationVariable identificationVariable = (IdentificationVariable)rangeVariableDeclaration.getIdentificationVariable(); String alias = identificationVariable.getText(); assertEquals(expectedAlias, alias); return jpqlExpression; } private void checkAliasesWhere(JPQLExpression jpqlExpression, String expectedAlias) { - SelectStatement selectStatement = (SelectStatement) jpqlExpression.getQueryStatement(); - WhereClause whereClause = (WhereClause) selectStatement.getWhereClause(); + SelectStatement selectStatement = (SelectStatement)jpqlExpression.getQueryStatement(); + WhereClause whereClause = (WhereClause)selectStatement.getWhereClause(); ListIterable expressions = whereClause.children(); List identificationVariableList = new ArrayList<>(); collectIdentificationVariables(expressions, identificationVariableList); - for (IdentificationVariable identificationVariable : identificationVariableList) { + for (IdentificationVariable identificationVariable: identificationVariableList) { if (expectedAlias.equals(identificationVariable.getText())) { assertEquals(expectedAlias, identificationVariable.getText()); } else { @@ -198,7 +136,7 @@ private void checkAliasesWhere(JPQLExpression jpqlExpression, String expectedAli } private void collectIdentificationVariables(ListIterable expressions, List identificationVariableList) { - for (Expression expression : expressions) { + for (Expression expression: expressions) { if (expression.children() != null) { collectIdentificationVariables(expression.children(), identificationVariableList); } diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/parser/JPQLParserTest.java b/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/parser/JPQLParserTest.java index 5ee690e8f2a..fce23df66e4 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/parser/JPQLParserTest.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/parser/JPQLParserTest.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2006, 2024 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 @@ -152,8 +151,6 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; -import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar3_2; - /** * This abstract class provides the functionality to test the parsed tree representation of a JPQL * query by traversing the tree and comparing each node. @@ -502,24 +499,6 @@ protected void testQuery(String jpqlQuery, testQuery(jpqlQuery, expressionTester, jpqlGrammar, jpqlQueryBNFId, formatter, tolerant); } - /** - * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression}) - * with the given tester, which is an equivalent representation of the parsed tree. - * - * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation - * @param expressionTester The tester used to verify the parsed tree is correctly representing the - * JPQL query - */ - protected void testJakartaDataQuery(String jpqlQuery, - ExpressionTester expressionTester) { - - JPQLExpression jpqlExpression = new JPQLExpression(jpqlQuery, JPQLGrammar3_2.instance(), JPQLStatementBNF.ID, true, true); - if (expressionTester.getClass() != JPQLExpressionTester.class) { - expressionTester = jpqlExpression(expressionTester); - } - expressionTester.test(jpqlExpression); - } - /** * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression}) * with the given tester, which is an equivalent representation of the parsed tree. diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/parser/JPQLParserTester.java b/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/parser/JPQLParserTester.java index 94feabe3bcc..45d223f0a3a 100644 --- a/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/parser/JPQLParserTester.java +++ b/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/parser/JPQLParserTester.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2024 Contributors to the Eclipse Foundation. 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 @@ -2854,7 +2853,7 @@ public static StateFieldPathExpressionTester path(String pathExpression) { int dotIndex = pathExpression.indexOf('.'); if (dotIndex == 0) { - return path(nullExpression(), false, pathExpression.substring(1)); + return path(nullExpression(), false, pathExpression); } String variable = pathExpression.substring(0, dotIndex);