eclipse-ee4j / eclipselink

Eclipselink project
https://eclipse.dev/eclipselink/
Other
196 stars 167 forks source link

IndexOutOfBoundsException when JPQL has embeddable or relational attributes without the optional entity identification variable #2188

Open anija-anil opened 2 months ago

anija-anil commented 2 months ago

JPQL queries with embeddable (or relation) attributes that omit the optional entity identification variable (for consistency with JDQL) get IndexOutOfBoundsException on EclipseLink.

Example query:

FROM Business WHERE location.address.city=?1 ORDER BY name

Here is the exception stack with IndexOutOfBoundsException:

Caused by: java.lang.IllegalArgumentException: An exception occurred while creating a query in EntityManager: 
Exception Description: Internal problem encountered while compiling [ FROM Business WHERE location.address.city=?1 ORDER BY name].
Internal Exception: java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
    at org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1848)
    at org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1869)
    at io.openliberty.data.internal.persistence.PageImpl.<init>(PageImpl.java:58)
    ... 39 more
Caused by: Exception [EclipseLink-0] (Eclipse Persistence Services - 5.0.0-B02.v202404111748): org.eclipse.persistence.exceptions.JPQLException
Exception Description: Internal problem encountered while compiling [ FROM Business WHERE location.address.city=?1 ORDER BY name].
Internal Exception: java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
    at org.eclipse.persistence.internal.jpa.jpql.HermesParser.buildUnexpectedException(HermesParser.java:215)
    at org.eclipse.persistence.internal.jpa.jpql.HermesParser.populateQueryImp(HermesParser.java:310)
    at org.eclipse.persistence.internal.jpa.jpql.HermesParser.buildQuery(HermesParser.java:174)
    at org.eclipse.persistence.internal.jpa.EJBQueryImpl.buildEJBQLDatabaseQuery(EJBQueryImpl.java:144)
    at org.eclipse.persistence.internal.jpa.EJBQueryImpl.buildEJBQLDatabaseQuery(EJBQueryImpl.java:120)
    at org.eclipse.persistence.internal.jpa.EJBQueryImpl.<init>(EJBQueryImpl.java:107)
    at org.eclipse.persistence.internal.jpa.EJBQueryImpl.<init>(EJBQueryImpl.java:91)
    at org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1846)
    ... 41 more
Caused by: java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
    at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:100)
    at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:106)
    at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:302)
    at java.base/java.util.Objects.checkIndex(Objects.java:385)
    at java.base/java.util.ArrayList.get(ArrayList.java:427)
    at org.eclipse.persistence.jpa.jpql.parser.AbstractPathExpression.getPath(AbstractPathExpression.java:258)
    at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor$PathResolver.resolvePath(ExpressionBuilderVisitor.java:2476)
    at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor$PathResolver.visitPathExpression(ExpressionBuilderVisitor.java:2675)
    at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor$PathResolver.visit(ExpressionBuilderVisitor.java:2620)
    at org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable.accept(IdentificationVariable.java:120)
    at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor$PathResolver.visitPathExpression(ExpressionBuilderVisitor.java:2650)
    at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor$PathResolver.visit(ExpressionBuilderVisitor.java:2620)
    at org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression.accept(StateFieldPathExpression.java:77)
    at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor.visitPathExpression(ExpressionBuilderVisitor.java:2127)
    at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor.visit(ExpressionBuilderVisitor.java:1893)
    at org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression.accept(StateFieldPathExpression.java:77)
    at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor$ComparisonExpressionVisitor.visit(ExpressionBuilderVisitor.java:2208)
    at org.eclipse.persistence.jpa.jpql.parser.AnonymousExpressionVisitor.visit(AnonymousExpressionVisitor.java:466)
    at org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression.accept(StateFieldPathExpression.java:77)
    at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor.visit(ExpressionBuilderVisitor.java:732)
    at org.eclipse.persistence.jpa.jpql.parser.ComparisonExpression.accept(ComparisonExpression.java:71)
    at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor.visit(ExpressionBuilderVisitor.java:2111)
    at org.eclipse.persistence.jpa.jpql.parser.WhereClause.accept(WhereClause.java:56)
    at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor.buildExpression(ExpressionBuilderVisitor.java:280)
    at org.eclipse.persistence.internal.jpa.jpql.JPQLQueryContext.buildExpression(JPQLQueryContext.java:326)
    at org.eclipse.persistence.internal.jpa.jpql.AbstractObjectLevelReadQueryVisitor.visit(AbstractObjectLevelReadQueryVisitor.java:226)
    at org.eclipse.persistence.jpa.jpql.parser.WhereClause.accept(WhereClause.java:56)
    at org.eclipse.persistence.internal.jpa.jpql.AbstractObjectLevelReadQueryVisitor.visitAbstractSelectStatement(AbstractObjectLevelReadQueryVisitor.java:292)
    at org.eclipse.persistence.internal.jpa.jpql.AbstractObjectLevelReadQueryVisitor.visit(AbstractObjectLevelReadQueryVisitor.java:160)
    at org.eclipse.persistence.jpa.jpql.parser.SelectStatement.accept(SelectStatement.java:100)
    at org.eclipse.persistence.internal.jpa.jpql.HermesParser$DatabaseQueryVisitor.visit(HermesParser.java:440)
    at org.eclipse.persistence.jpa.jpql.parser.SelectStatement.accept(SelectStatement.java:100)
    at org.eclipse.persistence.internal.jpa.jpql.HermesParser$DatabaseQueryVisitor.visit(HermesParser.java:423)
    at org.eclipse.persistence.jpa.jpql.parser.JPQLExpression.accept(JPQLExpression.java:186)
    at org.eclipse.persistence.internal.jpa.jpql.HermesParser.populateQueryImp(HermesParser.java:296)
    ... 47 more
Riva-Tholoor-Philip commented 1 month ago

I could recreate the issue.

Below is the Business entity we have by referencing the entity present in Jakarta Data.

@Entity
public class Business {

    public String name;

    @GeneratedValue
    @Id
    public int id;

    @Embedded
    public Location location;

    public static Business of(float latitude, float longitude, String city, String state, int zip,
                              int houseNum, String streetName, String streetDir, String name) {
        Business inst = new Business();
        Street street = new Street(streetName, streetDir);
        Address address = new Address(city, state, zip, houseNum, street);

        inst.name = name;
        inst.location = new Location(address, latitude, longitude);

        return inst;
    }

    @Embeddable
    public static class Location {

        @Embedded
        public Address address;

        @Column(columnDefinition = "DECIMAL(8,5) NOT NULL")
        public float latitude;

        @Column(columnDefinition = "DECIMAL(8,5) NOT NULL")
        public float longitude;

        public Location() {
        }

        public Location(Address address, float latitude, float longitude) {
            this.address = address;
            this.latitude = latitude;
            this.longitude = longitude;
        }
    }

    @Embeddable
    public static class Address {

        public String city;

        public int houseNum;

        public String state;

        @Embedded
        public Street street;

        public int zip;

        public Address() {
        }

        Address(String city, String state, int zip, int houseNum, Street street) {
            this.city = city;
            this.state = state;
            this.zip = zip;
            this.houseNum = houseNum;
            this.street = street;
        }
    }

    @Embeddable
    public static class Street {

        public String direction;

        @Column(name = "STREETNAME")
        public String name;

        public Street() {
        }

        public Street(String name, String direction) {
            this.name = name;
            this.direction = direction;
        }
    }

Here is the code where we executed the JPQL query:

public void testOLGH28931() throws Exception {
        Business ibmRoc = Business.of(44.05887f, -92.50355f, "Rochester", "Minnesota", 55901, 2800, "37th St", "NW", "IBM Rochester");
        Business ibmRTP = Business.of(35.90481f, -78.85026f, "Durham", "North Carolina", 27703, 4204, "Miami Blvd", "S", "IBM RTP");

        Business result;

        tx.begin();
        em.persist(ibmRoc);
        em.persist(ibmRTP);
        tx.commit();

        tx.begin();
        try {
            result = em.createQuery("FROM Business WHERE location.address.city=?1 ORDER BY name", Business.class)
                            .setParameter(1, "Rochester")
                            .getSingleResult();
            tx.commit();
        } catch (Exception e) {
            tx.rollback();

            throw e;
        }

This resulted in the following exception stack, which is the same as described in the issue:

Caused by: Exception [EclipseLink-0] (Eclipse Persistence Services - 5.0.0-B02.v202404111748): org.eclipse.persistence.exceptions.JPQLException
Exception Description: Internal problem encountered while compiling [FROM Business WHERE location.address.city=?1 ORDER BY name].
Internal Exception: java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
at org.eclipse.persistence.internal.jpa.jpql.HermesParser.buildUnexpectedException(HermesParser.java:215)
at org.eclipse.persistence.internal.jpa.jpql.HermesParser.populateQueryImp(HermesParser.java:310)
at org.eclipse.persistence.internal.jpa.jpql.HermesParser.buildQuery(HermesParser.java:174)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.buildEJBQLDatabaseQuery(EJBQueryImpl.java:144)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.buildEJBQLDatabaseQuery(EJBQueryImpl.java:120)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.<init>(EJBQueryImpl.java:107)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.<init>(EJBQueryImpl.java:91)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1846)
Caused by: java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:266)
at java.base/java.util.Objects.checkIndex(Objects.java:361)
at java.base/java.util.ArrayList.get(ArrayList.java:427)
at org.eclipse.persistence.jpa.jpql.parser.AbstractPathExpression.getPath(AbstractPathExpression.java:258)
at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor$PathResolver.resolvePath(ExpressionBuilderVisitor.java:2476)
at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor$PathResolver.visitPathExpression(ExpressionBuilderVisitor.java:2675)
at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor$PathResolver.visit(ExpressionBuilderVisitor.java:2620)
at org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable.accept(IdentificationVariable.java:120)
at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor$PathResolver.visitPathExpression(ExpressionBuilderVisitor.java:2650)
at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor$PathResolver.visit(ExpressionBuilderVisitor.java:2620)
at org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression.accept(StateFieldPathExpression.java:77)
at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor.visitPathExpression(ExpressionBuilderVisitor.java:2127)
at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor.visit(ExpressionBuilderVisitor.java:1893)
at org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression.accept(StateFieldPathExpression.java:77)
at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor$ComparisonExpressionVisitor.visit(ExpressionBuilderVisitor.java:2208)
at org.eclipse.persistence.jpa.jpql.parser.AnonymousExpressionVisitor.visit(AnonymousExpressionVisitor.java:466)
at org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression.accept(StateFieldPathExpression.java:77)
at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor.visit(ExpressionBuilderVisitor.java:732)
at org.eclipse.persistence.jpa.jpql.parser.ComparisonExpression.accept(ComparisonExpression.java:71)
at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor.visit(ExpressionBuilderVisitor.java:2111)
at org.eclipse.persistence.jpa.jpql.parser.WhereClause.accept(WhereClause.java:56)
at org.eclipse.persistence.internal.jpa.jpql.ExpressionBuilderVisitor.buildExpression(ExpressionBuilderVisitor.java:280)
at org.eclipse.persistence.internal.jpa.jpql.JPQLQueryContext.buildExpression(JPQLQueryContext.java:326)
at org.eclipse.persistence.internal.jpa.jpql.AbstractObjectLevelReadQueryVisitor.visit(AbstractObjectLevelReadQueryVisitor.java:226)
at org.eclipse.persistence.jpa.jpql.parser.WhereClause.accept(WhereClause.java:56)
at org.eclipse.persistence.internal.jpa.jpql.AbstractObjectLevelReadQueryVisitor.visitAbstractSelectStatement(AbstractObjectLevelReadQueryVisitor.java:292)
at org.eclipse.persistence.internal.jpa.jpql.AbstractObjectLevelReadQueryVisitor.visit(AbstractObjectLevelReadQueryVisitor.java:160)
at org.eclipse.persistence.jpa.jpql.parser.SelectStatement.accept(SelectStatement.java:100)
at org.eclipse.persistence.internal.jpa.jpql.HermesParser$DatabaseQueryVisitor.visit(HermesParser.java:440)
at org.eclipse.persistence.jpa.jpql.parser.SelectStatement.accept(SelectStatement.java:100)
at org.eclipse.persistence.internal.jpa.jpql.HermesParser$DatabaseQueryVisitor.visit(HermesParser.java:423)
at org.eclipse.persistence.jpa.jpql.parser.JPQLExpression.accept(JPQLExpression.java:186)
at org.eclipse.persistence.internal.jpa.jpql.HermesParser.populateQueryImp(HermesParser.java:296)

at componenttest.topology.utils.FATServletClient.assertTestResponse(FATServletClient.java:106)
at componenttest.topology.utils.FATServletClient.runTest(FATServletClient.java:91)
at componenttest.custom.junit.runner.SyntheticServletTest.invokeExplosively(SyntheticServletTest.java:49)
at componenttest.custom.junit.runner.FATRunner$1.evaluate(FATRunner.java:204)
at componenttest.custom.junit.runner.FATRunner$2.evaluate(FATRunner.java:364)
at componenttest.custom.junit.runner.FATRunner.run(FATRunner.java:178)
at org.testcontainers.containers.FailureDetectingExternalResource$1.evaluate(FailureDetectingExternalResource.java:29)
at componenttest.rules.repeater.RepeatTests$CompositeRepeatTestActionStatement.evaluate(RepeatTests.java:145)