jakartaee / persistence

https://jakartaee.github.io/persistence/
Other
206 stars 59 forks source link

Add support for Expressions as conditions in CriteriaBuilder CASE expressions #315

Closed dazey3 closed 2 years ago

dazey3 commented 3 years ago

Looking at the JPA API for CriteriaBuilder.SimpleCase<C,R>, there is no available support for Expressions to be passed as the condition of a WHEN clause.

There are currently only two ways to formulate a WHEN clause using the CriteriaBuilder.SimpleCase API:

    public static interface SimpleCase<C,R> extends Expression<R> {
        ...
        /**
        * Add a when/then clause to the case expression.
        * @param condition  "when" condition
        * @param result  "then" result value
        * @return simple case expression
        */
        SimpleCase<C, R> when(C condition, R result);

        /**
        * Add a when/then clause to the case expression.
        * @param condition  "when" condition
        * @param result  "then" result expression
        * @return simple case expression
        */
        SimpleCase<C, R> when(C condition, Expression<? extends R> result);

Notice that both methods only accept a condition of type C. The result can be of type Expression<? extends R>, but the condition must be of type C. I believe this is limiting and creates usecases that a user would expect to work, but are not supported.

A currently supported example:

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Object[]> cquery = cb.createQuery(Object[].class);
    Root<SimpleEntity> root = cquery.from(SimpleEntity.class);
    cquery.multiselect(root.get(SimpleEntity_.intVal1));

    Expression<Object> selectCase = cb.selectCase(root.get(SimpleEntity_.intVal2))
        .when(2, 20)
        .when(4, 40)
        .otherwise(60);

    cquery.where(cb.equal(root.get(SimpleEntity_.intVal1), selectCase));

A very simple example that passes constant values into the CASE select expression.

Unsupported example 1:

    Expression<Object> selectCase = cb.selectCase(root.get(SimpleEntity_.intVal2))
        .when(cb.literal(2), cb.literal(20))
        .when(cb.literal(4), cb.literal(40))
        .otherwise(cb.literal(60));

Unsupported example 2:

    ParameterExpression<Integer> checkParam1 = cb.parameter(Integer.class);
    ParameterExpression<Integer> checkParam2 = cb.parameter(Integer.class);
    ParameterExpression<Integer> resultParam1 = cb.parameter(Integer.class);
    ParameterExpression<Integer> resultParam2 = cb.parameter(Integer.class);
    ParameterExpression<Integer> resultParam3 = cb.parameter(Integer.class);

    Expression<Object> selectCase = cb.selectCase(root.get(SimpleEntity_.intVal2))
        .when(checkParam1, resultParam1)
        .when(checkParam2, resultParam2)
        .otherwise(resultParam3);

    cquery.where(cb.equal(root.get(SimpleEntity_.intVal1), selectCase));

    Query query = em.createQuery(cquery);
    query.setParameter(checkParam1, 2);
    query.setParameter(checkParam2, 4);
    query.setParameter(resultParam1, 20);
    query.setParameter(resultParam2, 40);
    query.setParameter(resultParam3, 60);

I think the lack of API methods in CriteriaBuilder.SimpleCase<C,R> can very easily expand to include:

        SimpleCase<C, R> when(Expression<? extends C> condition, R result);

        SimpleCase<C, R> when(Expression<? extends C> condition, Expression<? extends R> result);

This allows expression values (parameter and literal) to be passed as one would expect it to work

dazey3 commented 3 years ago

I would also add that you can rewrite these unsupported examples using CriteriaBuilder.Case API (without an operand) and they become supported.

Unsupported example 2 rewritten:

    ParameterExpression<Integer> checkParam1 = cb.parameter(Integer.class);
    ParameterExpression<Integer> checkParam2 = cb.parameter(Integer.class);
    ParameterExpression<Integer> resultParam1 = cb.parameter(Integer.class);
    ParameterExpression<Integer> resultParam2 = cb.parameter(Integer.class);
    ParameterExpression<Integer> resultParam3 = cb.parameter(Integer.class);

    Expression<Object> selectCase = cb.selectCase()
        .when(cb.equal(root.get(SimpleEntity_.intVal2), checkParam1), resultParam1)
        .when(cb.equal(root.get(SimpleEntity_.intVal2), checkParam2), resultParam2)
        .otherwise(resultParam3);

    cquery.where(cb.equal(root.get(SimpleEntity_.intVal1), selectCase));

    Query query = em.createQuery(cquery);
    query.setParameter(checkParam1, 2);
    query.setParameter(checkParam2, 4);
    query.setParameter(resultParam1, 20);
    query.setParameter(resultParam2, 40);
    query.setParameter(resultParam3, 60);

I believe both ways of writing should be considered equivalent.

lukasj commented 2 years ago

this is done, thanks!