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 <radek.felcman@oracle.com>
  • Loading branch information
rfelcman committed Apr 11, 2024
1 parent 2dd631c commit 78a5206
Show file tree
Hide file tree
Showing 20 changed files with 614 additions and 52 deletions.
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
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
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>
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
@@ -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));
}
}
@@ -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

0 comments on commit 78a5206

Please sign in to comment.