Skip to content

Commit

Permalink
Jakarta Persistence 3.2: JPQL generate missing Entity default this
Browse files Browse the repository at this point in the history
…alias in simple SELECT queries (#2102)

This improvement ensure, that JPQL SELECT queries like `SELECT this FROM Entity` where entity alias is not specified in the `FROM` part will be automatically added. Default `this` alias is used as it specified by:
jakartaee/persistence#452
There is automatic `SELECT this` generation for queries like `FROM Entity this` too.
This happens only for EclipseLink 5.0.0 and higher or for persistence property `eclipselink.jpql.validation` with value `None` or `JPA 3.2`.

It allows accept following queries like:

- `SELECT this FROM Entity` -> `SELECT this FROM Entity this`
- `SELECT COUNT(this) FROM Entity` -> `SELECT COUNT(this) FROM Entity this`
- `SELECT this FROM Entity this WHERE id = :id` -> `SELECT this FROM Entity this WHERE this.id = :id`
- `SELECT this FROM Entity WHERE id = :id AND UPPER(name) = 'NAME 1` -> `SELECT this FROM Entity this WHERE this.id = :id AND UPPER(this.name) = 'NAME 1'`
- `FROM Entity this` -> `SELECT this FROM Entity this`

Some test modifications in the `org.eclipse.persistence.jpa.testapps` module:

- `org.eclipse.persistence.testing.tests.jpa.jpql.advanced.JUnitJPQLComplexTest#testNoSelect`
- `org.eclipse.persistence.testing.tests.jpa.jpql.advanced.JUnitJPQLValidationTest#noAliasWithWHEREAndParameterExceptionTest`
- org.eclipse.persistence.testing.tests.jpql.JPQLExceptionTest#noAliasWithWHEREAndParameterExceptionTest (CORE)

Signed-off-by: Radek Felcman <[email protected]>
  • Loading branch information
rfelcman authored Apr 11, 2024
1 parent 2dd631c commit 78a5206
Show file tree
Hide file tree
Showing 20 changed files with 614 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,6 @@ public static JPQLExceptionTest badAliasExceptionTest() {
return theTest;
}

//This test produced a stack overflow in the Beta of Pine
public static JPQLExceptionTest noAliasWithWHEREAndParameterExceptionTest() {
JPQLExceptionTest theTest = new JPQLExceptionTest();
theTest.expectedException = JPQLException.unexpectedToken(null, 0, 0, null, null);
theTest.setEjbqlString("FROM Employee WHERE firstName = ?1");
theTest.setName("No Alias With WHERE and Parameter Exception Test");
return theTest;
}

public static JPQLExceptionTest generalExceptionTest() {
JPQLExceptionTest theTest = new JPQLExceptionTest();
theTest.expectedException = JPQLException.unexpectedToken(null, 0, 0, null, null);
Expand Down Expand Up @@ -164,7 +155,6 @@ public static void addTestsTo(TestSuite theSuite) {
// theSuite.addTest(EJBQLExceptionTest.expressionNotSupportedTest());
// Removed by JED - Member of is now supported
// theSuite.addTest(EJBQLExceptionTest.memberOfNotSupportedTest());
theSuite.addTest(JPQLExceptionTest.noAliasWithWHEREAndParameterExceptionTest());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
// - Issue 1885: Implement new JPQLGrammar for upcoming Jakarta Persistence 3.2
package org.eclipse.persistence.internal.jpa.jpql;

import org.eclipse.persistence.Version;
import org.eclipse.persistence.config.ParserValidationType;
import org.eclipse.persistence.exceptions.JPQLException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.internal.expressions.ParameterExpression;
import org.eclipse.persistence.internal.queries.JPQLCallQueryMechanism;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.jpa.jpql.EclipseLinkGrammarValidator;
import org.eclipse.persistence.jpa.jpql.EclipseLinkVersion;
import org.eclipse.persistence.jpa.jpql.JPQLQueryProblem;
import org.eclipse.persistence.jpa.jpql.JPQLQueryProblemResourceBundle;
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor;
Expand All @@ -41,6 +43,7 @@
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar3_0;
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar3_1;
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar3_2;
import org.eclipse.persistence.jpa.jpql.parser.JPQLStatementBNF;
import org.eclipse.persistence.jpa.jpql.parser.SelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.UpdateStatement;
import org.eclipse.persistence.queries.DatabaseQuery;
Expand Down Expand Up @@ -246,6 +249,17 @@ private JPQLGrammar jpqlGrammar() {
};
}

private boolean isJakartaDataValidationLevel() {
if (validationLevel != null) {
return switch (validationLevel) {
case ParserValidationType.JPA32, ParserValidationType.None -> true;
default -> false;
};
} else {
return false;
}
}

@Override
public void populateQuery(CharSequence jpqlQuery, DatabaseQuery query, AbstractSession session) {
populateQueryImp(jpqlQuery, query, session);
Expand All @@ -256,11 +270,17 @@ private DatabaseQuery populateQueryImp(CharSequence jpqlQuery,
AbstractSession session) {

try {
String version = Version.getVersion();
String majorMinorVersion = version.substring(0, version.indexOf(".", version.indexOf(".") + 1));
EclipseLinkVersion elVersion = EclipseLinkVersion.value(majorMinorVersion);
boolean isJakartaDataVersion = elVersion.isNewerThanOrEqual(EclipseLinkVersion.VERSION_5_0) || isJakartaDataValidationLevel();
// Parse the JPQL query with the most recent JPQL grammar
JPQLExpression jpqlExpression = new JPQLExpression(
jpqlQuery,
DefaultEclipseLinkJPQLGrammar.instance(),
isTolerant()
JPQLStatementBNF.ID,
isTolerant(),
isJakartaDataVersion
);

// Create a context that caches the information contained in the JPQL query
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,4 +324,14 @@
<property name="eclipselink.logging.parameters" value="${eclipselink.logging.parameters}"/>
</properties>
</persistence-unit>
<persistence-unit name="advanced-jakartadata" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>org.eclipse.persistence.testing.models.jpa.datatypes.WrapperTypes</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="eclipselink.logging.level" value="${eclipselink.logging.level}"/>
<property name="eclipselink.logging.level.sql" value="${eclipselink.logging.sql.level}"/>
<property name="eclipselink.logging.parameters" value="${eclipselink.logging.parameters}"/>
</properties>
</persistence-unit>
</persistence>
Original file line number Diff line number Diff line change
Expand Up @@ -3888,9 +3888,7 @@ public void testNestedArrays2() {
// Test JPQL with no select clause.
public void testNoSelect() {
EntityManager em = createEntityManager();
Query query = em.createQuery("from Employee e where e.firstName = 'Bob'");
query.getResultList();
query = em.createQuery("from Employee e join e.address a where a.city = 'Ottawa'");
Query query = em.createQuery("from Employee this where this.firstName = 'Bob'");
query.getResultList();
closeEntityManager(em);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/*
* Copyright (c) 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 from Oracle TopLink
package org.eclipse.persistence.testing.tests.jpa.jpql.advanced;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.eclipse.persistence.testing.framework.jpa.junit.JUnitTestCase;
import org.eclipse.persistence.testing.models.jpa.advanced.EmployeePopulator;
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;
import org.junit.Assert;

import java.math.BigDecimal;
import java.math.BigInteger;

/**
* <p>
* <b>Purpose</b>: Test Entity alias generation EJBQL functionality.
* <p>
* <b>Description</b>: This class creates a test suite, initializes the database
* and adds tests to the suite.
* <p>
* <b>Responsibilities</b>:
* <ul>
* <li> Run tests for alias generation EJBQL functionality
* </ul>
* @see EmployeePopulator
* @see JUnitDomainObjectComparer
*/
public class JUnitJPQLJakartaDataNoAliasTest extends JUnitTestCase {
private static final String STRING_DATA = "A String";

private static int wrapperId;

static JUnitDomainObjectComparer comparer; //the global comparer object used in all tests

public JUnitJPQLJakartaDataNoAliasTest() {
super();
}

public JUnitJPQLJakartaDataNoAliasTest(String name) {
super(name);
setPuName(getPersistenceUnitName());
}

@Override
public String getPersistenceUnitName() {
return "advanced-jakartadata";
}

//This method is run at the end of EVERY test case method
@Override
public void tearDown() {
clearCache();
}

//This suite contains all tests contained in this class
public static Test suite() {
TestSuite suite = new TestSuite();
suite.setName("JUnitJPQLInheritanceTest");
suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testSetup"));
suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testNoAlias"));
suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testNoAliasOBJECT"));
suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testNoAliasCOUNT"));
suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testNoAliasCASTCOUNT"));
suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testCorrectAliases"));
suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testNoAliasWhere"));
suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testNoAliasFromWhere"));
suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testNoAliasFromWhereAnd"));
suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testNoAliasFromWhereAndUPPER"));
suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testGeneratedSelectNoAliasFromWhere"));
suite.addTest(new JUnitJPQLJakartaDataNoAliasTest("testGeneratedSelect"));
return suite;
}

/**
* The setup is done as a test, both to record its failure, and to allow execution in the server.
*/
public void testSetup() {
//initialize the global comparer object
comparer = new JUnitDomainObjectComparer();
//set the session for the comparer to use
comparer.setSession(getPersistenceUnitServerSession());

new DataTypesTableCreator().replaceTables(getPersistenceUnitServerSession());
clearCache();
EntityManager em = createEntityManager();
WrapperTypes wt;

beginTransaction(em);
wt = new WrapperTypes(BigDecimal.ZERO, BigInteger.ZERO, Boolean.FALSE,
Byte.valueOf("0"), 'A', Short.valueOf("0"),
0, 0L, 0.0f, 0.0, STRING_DATA);
em.persist(wt);
wrapperId = wt.getId();
commitTransaction(em);
closeEntityManager(em);
}

public void testNoAlias() {
EntityManager em = createEntityManager();

WrapperTypes wrapperTypes = (WrapperTypes) em.createQuery("SELECT this FROM WrapperTypes").getResultList().get(0);
clearCache();
ReadObjectQuery tlQuery = new ReadObjectQuery(WrapperTypes.class);
tlQuery.setSelectionCriteria(tlQuery.getExpressionBuilder().get("id").equal(wrapperId));

WrapperTypes tlWrapperTypes = (WrapperTypes) getPersistenceUnitServerSession().executeQuery(tlQuery);
Assert.assertTrue("NoAlias Test Failed", comparer.compareObjects(wrapperTypes, tlWrapperTypes));
}

public void testNoAliasOBJECT() {
EntityManager em = createEntityManager();

WrapperTypes wrapperTypes = (WrapperTypes) em.createQuery("SELECT OBJECT(this) FROM WrapperTypes").getResultList().get(0);
clearCache();
ReadObjectQuery tlQuery = new ReadObjectQuery(WrapperTypes.class);
tlQuery.setSelectionCriteria(tlQuery.getExpressionBuilder().get("id").equal(wrapperId));

WrapperTypes tlWrapperTypes = (WrapperTypes) getPersistenceUnitServerSession().executeQuery(tlQuery);
Assert.assertTrue("NoAliasOBJECT Test Failed", comparer.compareObjects(wrapperTypes, tlWrapperTypes));
}

public void testNoAliasCOUNT() {
EntityManager em = createEntityManager();

long result = em.createQuery("SELECT COUNT(this) FROM WrapperTypes", Long.class).getSingleResult();
Assert.assertTrue("NoAliasCOUNT Test Failed", result > 0L);
}

public void testNoAliasCASTCOUNT() {
EntityManager em = createEntityManager();

String result = em.createQuery("SELECT CAST(COUNT(this) AS CHAR) FROM WrapperTypes", String.class).getSingleResult();
Assert.assertTrue("NoAliasCOUNT Test Failed", result.length() > 0);
}

public void testCorrectAliases() {
EntityManager em = createEntityManager();

WrapperTypes wrapperTypes = (WrapperTypes) em.createQuery("SELECT this FROM WrapperTypes this").getResultList().get(0);
clearCache();
ReadObjectQuery tlQuery = new ReadObjectQuery(WrapperTypes.class);
tlQuery.setSelectionCriteria(tlQuery.getExpressionBuilder().get("id").equal(wrapperId));

WrapperTypes tlWrapperTypes = (WrapperTypes) getPersistenceUnitServerSession().executeQuery(tlQuery);
Assert.assertTrue("CorrectAliases Test Failed", comparer.compareObjects(wrapperTypes, tlWrapperTypes));
}

public void testNoAliasWhere() {
EntityManager em = createEntityManager();

Query wrapperTypesQuery = em.createQuery("SELECT this FROM WrapperTypes this WHERE id = :idParam");
wrapperTypesQuery.setParameter("idParam", wrapperId);
WrapperTypes wrapperTypes = (WrapperTypes) wrapperTypesQuery.getResultList().get(0);
clearCache();
ReadObjectQuery tlQuery = new ReadObjectQuery(WrapperTypes.class);
tlQuery.setSelectionCriteria(tlQuery.getExpressionBuilder().get("id").equal(wrapperId));

WrapperTypes tlWrapperTypes = (WrapperTypes) getPersistenceUnitServerSession().executeQuery(tlQuery);
Assert.assertTrue("NoAliasWhere Test Failed", comparer.compareObjects(wrapperTypes, tlWrapperTypes));
}

public void testNoAliasFromWhere() {
EntityManager em = createEntityManager();

Query wrapperTypesQuery = em.createQuery("SELECT this FROM WrapperTypes WHERE id = :idParam");
wrapperTypesQuery.setParameter("idParam", wrapperId);
WrapperTypes wrapperTypes = (WrapperTypes) wrapperTypesQuery.getResultList().get(0);
clearCache();
ReadObjectQuery tlQuery = new ReadObjectQuery(WrapperTypes.class);
tlQuery.setSelectionCriteria(tlQuery.getExpressionBuilder().get("id").equal(wrapperId));

WrapperTypes tlWrapperTypes = (WrapperTypes) getPersistenceUnitServerSession().executeQuery(tlQuery);
Assert.assertTrue("NoAliasFromWhere Test Failed", comparer.compareObjects(wrapperTypes, tlWrapperTypes));
}

public void testNoAliasFromWhereAnd() {
EntityManager em = createEntityManager();

Query wrapperTypesQuery = em.createQuery("SELECT this FROM WrapperTypes WHERE id = :idParam AND stringData = :stringDataParam");
wrapperTypesQuery.setParameter("idParam", wrapperId);
wrapperTypesQuery.setParameter("stringDataParam", STRING_DATA);
WrapperTypes wrapperTypes = (WrapperTypes) wrapperTypesQuery.getResultList().get(0);
clearCache();
ReadObjectQuery tlQuery = new ReadObjectQuery(WrapperTypes.class);
tlQuery.setSelectionCriteria(tlQuery.getExpressionBuilder().get("id").equal(wrapperId));

WrapperTypes tlWrapperTypes = (WrapperTypes) getPersistenceUnitServerSession().executeQuery(tlQuery);
Assert.assertTrue("NoAliasFromWhereAnd Test Failed", comparer.compareObjects(wrapperTypes, tlWrapperTypes));
}

public void testNoAliasFromWhereAndUPPER() {
EntityManager em = createEntityManager();

Query wrapperTypesQuery = em.createQuery("SELECT this FROM WrapperTypes WHERE id = :idParam AND UPPER(stringData) = :stringDataParam");
wrapperTypesQuery.setParameter("idParam", wrapperId);
wrapperTypesQuery.setParameter("stringDataParam", STRING_DATA.toUpperCase());
WrapperTypes wrapperTypes = (WrapperTypes) wrapperTypesQuery.getResultList().get(0);
clearCache();
ReadObjectQuery tlQuery = new ReadObjectQuery(WrapperTypes.class);
tlQuery.setSelectionCriteria(tlQuery.getExpressionBuilder().get("id").equal(wrapperId));

WrapperTypes tlWrapperTypes = (WrapperTypes) getPersistenceUnitServerSession().executeQuery(tlQuery);
Assert.assertTrue("NoAliasFromWhereAndUPPER Test Failed", comparer.compareObjects(wrapperTypes, tlWrapperTypes));
}

public void testGeneratedSelect() {
EntityManager em = createEntityManager();

WrapperTypes wrapperTypes = (WrapperTypes) em.createQuery("FROM WrapperTypes").getResultList().get(0);
clearCache();
ReadObjectQuery tlQuery = new ReadObjectQuery(WrapperTypes.class);
tlQuery.setSelectionCriteria(tlQuery.getExpressionBuilder().get("id").equal(wrapperId));

WrapperTypes tlWrapperTypes = (WrapperTypes) getPersistenceUnitServerSession().executeQuery(tlQuery);
Assert.assertTrue("NoAlias Test Failed", comparer.compareObjects(wrapperTypes, tlWrapperTypes));
}

public void testGeneratedSelectNoAliasFromWhere() {
EntityManager em = createEntityManager();

Query wrapperTypesQuery = em.createQuery("FROM WrapperTypes WHERE id = :idParam");
wrapperTypesQuery.setParameter("idParam", wrapperId);
WrapperTypes wrapperTypes = (WrapperTypes) wrapperTypesQuery.getResultList().get(0);
clearCache();
ReadObjectQuery tlQuery = new ReadObjectQuery(WrapperTypes.class);
tlQuery.setSelectionCriteria(tlQuery.getExpressionBuilder().get("id").equal(wrapperId));

WrapperTypes tlWrapperTypes = (WrapperTypes) getPersistenceUnitServerSession().executeQuery(tlQuery);
Assert.assertTrue("GeneratedSelectNoAliasFromWhere Test Failed", comparer.compareObjects(wrapperTypes, tlWrapperTypes));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
Expand Down Expand Up @@ -517,7 +517,7 @@ public void malformedJPQLExceptionTest8()
public void noAliasWithWHEREAndParameterExceptionTest()
{

String ejbqlString = "FROM Employee WHERE firstName = ?1";
String ejbqlString = "FROM Employee e WHERE firstName = ?1";

try
{
Expand Down
Loading

0 comments on commit 78a5206

Please sign in to comment.