Blazebit / blaze-persistence

Rich Criteria API for JPA providers
https://persistence.blazebit.com
Apache License 2.0
742 stars 90 forks source link

Error with subtype association join using querydsl integration #1944

Open azizairo opened 1 month ago

azizairo commented 1 month ago

Hi, I am getting the error: Attribute not found while trying to join subtype associations. Here is my query:

blazeJPAQueryFactory.with(maxLessonContentVersionCTE, subquery1)
        .select(moduleUnit)
        .from(moduleUnit)
        .leftJoin(moduleUnit.as(QLesson.class).contents, lessonContent).fetchJoin()
        .fetchOne();

If i use querydsl without blazepersistance, this query will work

jwgmeligmeyling commented 1 month ago

I am not familiar with the .as(QClass) syntax, but contrary to HQL/JPQL, in JPQLNext I believe sub-type attributes are not present on the parent type. Instead the expression needs to be explicitly treated:

JPQLNextExpressions.treat(moduleUnit, QLesson.class)

Would be the syntax for that I guess

azizairo commented 1 month ago

As I understood from method treat, method does not give us possibility to treat parent classes as their subtypes

public static <T> Expression<T> treat(Class<T> result, Expression<?> expression) {
        if (Boolean.class.equals(result) || boolean.class.equals(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_BOOLEAN, expression);
        } else if (Byte.class.equals(result) || byte.class.equals(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_BYTE, expression);
        } else if (Short.class.equals(result) || short.class.equals(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_SHORT, expression);
        } else if (Long.class.equals(result) || long.class.equals(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_LONG, expression);
        } else if (Integer.class.equals(result) || int.class.equals(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_INTEGER, expression);
        } else if (Float.class.equals(result) || float.class.equals(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_FLOAT, expression);
        } else if (Double.class.equals(result) || double.class.equals(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_DOUBLE, expression);
        } else if (Character.class.equals(result) || char.class.equals(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_CHARACTER, expression);
        } else if (String.class.equals(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_STRING, expression);
        } else if (BigInteger.class.equals(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_BIGINTEGER, expression);
        } else if (BigDecimal.class.equals(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_BIGDECIMAL, expression);
        } else if (Calendar.class.equals(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_CALENDAR, expression);
        } else if (Timestamp.class.isAssignableFrom(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_TIMESTAMP, expression);
        } else if (Time.class.isAssignableFrom(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_TIME, expression);
        } else if (Date.class.isAssignableFrom(result)) {
            return Expressions.simpleOperation(result, JPQLNextOps.TREAT_DATE, expression);
        } else {
            throw new IllegalArgumentException("No cast operation for " + result.getName());
        }
    }

.as(QClass) casts the path to a subtype querytype, so with it i can join subtype association. But it does not work with BlazePersistance

jwgmeligmeyling commented 1 month ago

Treat should do precisely that, although I apparently mixed up the parameters:

JPQLNextExpressions.treat(QLesson.class, moduleUnit) should complile and return a QLesson instance (which has the contents) field.

jwgmeligmeyling commented 1 month ago

The issue with moduleUnit.as(QLesson.class) is that it doesn't actually render the cast in the query but rather just relies on subTypeField being present in the ParentEntity anyway. This behaviour is however Hibernate specific (or maybe JPA?) and Blaze-Persist decided to require explicit treat operations.

azizairo commented 1 month ago

JPQLNextExpressions.treat() method returns Expression type and it can not be cast to QLesson classes.

public static <T> Expression<T> treat(Class<T> result, Expression<?> expression) {...}
azizairo commented 1 month ago

I found the correct treat method that can cast parents to the specified subtype. Thank a lot! P.S.: The correct method was in JPAExpressions.class