eclipselink icon indicating copy to clipboard operation
eclipselink copied to clipboard

Regression in 2.7.5/3.0.2: Wrong parameter type expected in "coalesce"

Open igormukhin opened this issue 3 years ago • 4 comments

EclipseLink 2.7.9/3.0.2 Any database platform.

String jsql = "select c from Country c where c.name is not null and coalesce(c.iso2, :p1) != 'DE'";
TypedQuery<Country> query = em.createQuery(jsql, Country.class);
query.setParameter("p1", "DE");  // exception!!

This code throws this exception:

Exception in thread "main" java.lang.IllegalArgumentException: You have attempted to set a value of type class java.lang.String for parameter p1 with expected type of class java.lang.Boolean from query string select c from Country c where c.name is not null and coalesce(c.iso2, :p1) != 'DE'.
	at org.eclipse.persistence.internal.jpa.QueryImpl.setParameterInternal(QueryImpl.java:946)
	at org.eclipse.persistence.internal.jpa.EJBQueryImpl.setParameter(EJBQueryImpl.java:609)
	at de.wirthedv.test.eclipselink.TestExistsWithCoalesce.test(TestExistsWithCoalesce.java:24)
	at de.wirthedv.test.eclipselink.util.AbstractTestCase.run(AbstractTestCase.java:22)
	at de.wirthedv.test.eclipselink.TestExistsWithCoalesce.main(TestExistsWithCoalesce.java:13)

This bug was introduced in version 2.7.5. I tested with 2.7.4 and below, it is working. I tested with 2.7.5, it is failing.

I tried different queries to localize the issue. The issue arises when you have a coalesce with a parameter (first or second parameter) and an expression before that (in the example it is c.name is not null).

igormukhin avatar Jul 08 '21 09:07 igormukhin

The bug was introduced in the commit: 8a232d422c15b1d15e757aed1282edc4754c1c9e File: ExpressionBuilderVisitor.java Method: public void visit(CoalesceExpression expression) {

The line Class<?> coalesceType = type[0]; was introduced, which caches the wrong type. The correct type is not known at this point in time. It will be known only inside the following for-loop.

igormukhin avatar Jul 08 '21 15:07 igormukhin

I think the problematic method should look like this:

    @Override
    public void visit(CoalesceExpression expression) {

        List<Expression> expressions = new ArrayList<Expression>();
        List<Class<?>> types = new LinkedList<Class<?>>();

        // Create the Expression for each scalar expression
        for (org.eclipse.persistence.jpa.jpql.parser.Expression child : expression.getExpression().children()) {
            child.accept(this);
            expressions.add(queryExpression);
            types.add(type[0]);
        }

        // Create the COALESCE expression
        queryExpression = queryContext.getBaseExpression();
        queryExpression = queryExpression.coalesce(expressions);

        // Set the expression type
        type[0] = queryContext.typeResolver().compareCollectionEquivalentTypes(types);
    }

I removed the section where it tries to guess parameters types, because:

  • In both cases it was implemented incorrectly (in 2.7.4 it was doing nothing, in 2.7.5 it was assigning the wrong type)
  • You can't guess a correct type. Even if the type of one of the parameters is known (like c.iso2 in my example), you still cannot use this type, because the field could use a converter, so the parameter type for another argument would be different.

igormukhin avatar Jul 09 '21 07:07 igormukhin

Workaround for those who needs to use the newer version of EL -- flip the expression so that the previous type is the correct type.

String jsql = "select c from Country c where c.name is not null and 'DE' != coalesce(c.iso2, :p1)";

igormukhin avatar Jul 09 '21 07:07 igormukhin

I also reported the bug here https://bugs.eclipse.org/bugs/show_bug.cgi?id=575107

igormukhin avatar Jul 29 '21 08:07 igormukhin