From 60e2f3a901af5c543a7b9c462360cdfb8ed30dea Mon Sep 17 00:00:00 2001 From: Jan-Willem Gmelig Meyling Date: Sat, 15 Feb 2020 14:32:38 +0100 Subject: [PATCH] [#658] Support JPQL Treated paths for WHERE clause JPA 2.1 introduced the TREAT operator for explicit casting. Initial support for TREAT in the FROM clause was added in PR #705. However, the specification also describes the notion of `treated_subpath` expressions (chapter 4.4.4.1) that are allowed in the WHERE clause of a query. The syntax is as follows: > ``` > treated_subpath ::= TREAT(general_subpath AS subtype) > > single_valued_path_expression ::= qualified_identification_variable | TREAT(qualified_identification_variable AS subtype) | state_field_path_expression | single_valued_object_path_expression > ``` And can be used as such: ```SQL SELECT e FROM Employee e JOIN e.projects p WHERE TREAT(p AS LargeProject).budget > 1000 ``` (Example from chapter 4.4.9). This pull request adds the support for treated subpaths in QueryDSL. It does so by introducing a new `PathType` and convenience method `JPAExpressions.treat`. --- pom.xml | 3 ++ .../com/querydsl/core/types/PathType.java | 7 ++- .../java/com/querydsl/jpa/JPAExpressions.java | 49 +++++++++++++++++++ .../java/com/querydsl/jpa/JPQLTemplates.java | 1 + .../com/querydsl/jpa/JPQLSerializerTest.java | 16 ++++++ 5 files changed, 75 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cb3d988a8..c77280ac9 100644 --- a/pom.xml +++ b/pom.xml @@ -291,6 +291,9 @@ com/querydsl/sql/types/LocalDateTimeType com/querydsl/sql/types/LocalDateType com/querydsl/sql/types/LocalTimeType + com/querydsl/jpa/Hibernate5Templates + com/querydsl/jpa/HibernateHandler + com/querydsl/jpa/JPAExpressions BACKWARD_COMPATIBLE_USER true diff --git a/querydsl-core/src/main/java/com/querydsl/core/types/PathType.java b/querydsl-core/src/main/java/com/querydsl/core/types/PathType.java index 09b5cf9aa..14d7bf731 100644 --- a/querydsl-core/src/main/java/com/querydsl/core/types/PathType.java +++ b/querydsl-core/src/main/java/com/querydsl/core/types/PathType.java @@ -65,7 +65,12 @@ public enum PathType implements Operator { /** * Root path */ - VARIABLE; + VARIABLE, + + /** + * Treated path + */ + TREATED_PATH; @Override public Class getType() { diff --git a/querydsl-jpa/src/main/java/com/querydsl/jpa/JPAExpressions.java b/querydsl-jpa/src/main/java/com/querydsl/jpa/JPAExpressions.java index f2a99fe1d..2044e2a7f 100644 --- a/querydsl-jpa/src/main/java/com/querydsl/jpa/JPAExpressions.java +++ b/querydsl-jpa/src/main/java/com/querydsl/jpa/JPAExpressions.java @@ -17,11 +17,19 @@ import com.querydsl.core.Tuple; import com.querydsl.core.types.CollectionExpression; import com.querydsl.core.types.EntityPath; import com.querydsl.core.types.Expression; +import com.querydsl.core.types.ExpressionException; import com.querydsl.core.types.Ops; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.PathType; +import com.querydsl.core.types.dsl.BeanPath; import com.querydsl.core.types.dsl.ComparableExpression; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.StringExpression; +import javax.persistence.Entity; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; + /** * {@code JPAExpressions} provides factory methods for JPQL specific operations * elements. @@ -102,6 +110,31 @@ public final class JPAExpressions { return select(expr).from(expr); } + /** + * Create a JPA 2.1 treated path. + * + * @param path The path to apply the treat operation on + * @param subtype subtype class + * @param the subtype class + * @param the expression type + * @return subtype instance with the same identity + */ + public static , T> U treat(BeanPath path, Class subtype) { + try { + Class entitySubType = getConcreteEntitySubType(subtype); + PathMetadata pathMetadata = new PathMetadata(path, getEntityName(entitySubType), PathType.TREATED_PATH); + return subtype.getConstructor(PathMetadata.class).newInstance(pathMetadata); + } catch (InstantiationException e) { + throw new ExpressionException(e.getMessage(), e); + } catch (IllegalAccessException e) { + throw new ExpressionException(e.getMessage(), e); + } catch (InvocationTargetException e) { + throw new ExpressionException(e.getMessage(), e); + } catch (NoSuchMethodException e) { + throw new ExpressionException(e.getMessage(), e); + } + } + /** * Create a avg(col) expression * @@ -142,6 +175,22 @@ public final class JPAExpressions { return Expressions.stringOperation(JPQLOps.TYPE, path); } + private static String getEntityName(Class clazz) { + final Entity entityAnnotation = clazz.getAnnotation(Entity.class); + if (entityAnnotation != null && entityAnnotation.name().length() > 0) { + return entityAnnotation.name(); + } else if (clazz.getPackage() != null) { + String pn = clazz.getPackage().getName(); + return clazz.getName().substring(pn.length() + 1); + } else { + return clazz.getName(); + } + } + + private static , T> Class getConcreteEntitySubType(Class subtype) { + return (Class) ((ParameterizedType) subtype.getGenericSuperclass()).getActualTypeArguments()[0]; + } + private JPAExpressions() { } } diff --git a/querydsl-jpa/src/main/java/com/querydsl/jpa/JPQLTemplates.java b/querydsl-jpa/src/main/java/com/querydsl/jpa/JPQLTemplates.java index 2a695868c..6f593bb88 100644 --- a/querydsl-jpa/src/main/java/com/querydsl/jpa/JPQLTemplates.java +++ b/querydsl-jpa/src/main/java/com/querydsl/jpa/JPQLTemplates.java @@ -144,6 +144,7 @@ public class JPQLTemplates extends Templates { // path types add(PathType.PROPERTY, "{0}.{1s}"); add(PathType.VARIABLE, "{0s}"); + add(PathType.TREATED_PATH, "treat({0} as {1s})"); // case for eq add(Ops.CASE_EQ, "case {1} end"); diff --git a/querydsl-jpa/src/test/java/com/querydsl/jpa/JPQLSerializerTest.java b/querydsl-jpa/src/test/java/com/querydsl/jpa/JPQLSerializerTest.java index 575dbb066..2cf3b002c 100644 --- a/querydsl-jpa/src/test/java/com/querydsl/jpa/JPQLSerializerTest.java +++ b/querydsl-jpa/src/test/java/com/querydsl/jpa/JPQLSerializerTest.java @@ -23,6 +23,7 @@ import org.junit.Test; import com.querydsl.core.DefaultQueryMetadata; import com.querydsl.core.JoinType; import com.querydsl.core.QueryMetadata; +import com.querydsl.core.domain.QAnimal; import com.querydsl.core.domain.QCat; import com.querydsl.core.types.EntityPath; import com.querydsl.core.types.Expression; @@ -253,6 +254,21 @@ public class JPQLSerializerTest { " inner join treat(cat.mate as DomesticCat) as domesticCat", serializer.toString()); } + @Test + public void treated_path() { + QAnimal animal = QAnimal.animal; + JPQLSerializer serializer = new JPQLSerializer(HQLTemplates.DEFAULT); + QueryMetadata md = new DefaultQueryMetadata(); + md.addJoin(JoinType.DEFAULT, animal); + + md.addWhere(JPAExpressions.treat(animal, QCat.class).breed.eq(1)); + md.setProjection(animal); + serializer.serialize(md, false, null); + assertEquals("select animal\n" + + "from Animal animal\n" + + "where treat(animal as Cat).breed = ?1", serializer.toString()); + } + @Test public void openJPA_variables() { QCat cat = QCat.cat;