From f2fdfd3936902e3b66bc0e2d06352776e0b9f1a6 Mon Sep 17 00:00:00 2001 From: Jan-Willem Gmelig Meyling Date: Thu, 26 Dec 2019 23:31:42 +0100 Subject: [PATCH 1/6] CTE criteria --- .../criteria/BlazeAbstractQuery.java | 2 + .../criteria/BlazeCTECriteria.java | 116 +++++++++++ .../criteria/impl/BlazeCTECriteriaImpl.java | 194 ++++++++++++++++++ .../criteria/impl/BlazeCriteriaQueryImpl.java | 6 + 4 files changed, 318 insertions(+) create mode 100644 jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java create mode 100644 jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java diff --git a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeAbstractQuery.java b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeAbstractQuery.java index bbb508c7ce..1df1c719d3 100644 --- a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeAbstractQuery.java +++ b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeAbstractQuery.java @@ -37,6 +37,8 @@ public interface BlazeAbstractQuery extends AbstractQuery, BlazeCommonAbst // TODO: integrate support for default join nodes? // TODO: maybe add explicit support for limit? + public BlazeCTECriteria with(Class clasz); + /** * Like {@link AbstractQuery#from(Class)} but allows to set the alias of the {@link BlazeRoot}. * diff --git a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java new file mode 100644 index 0000000000..10592a9c66 --- /dev/null +++ b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java @@ -0,0 +1,116 @@ +/* + * Copyright 2014 - 2019 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.criteria; + +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import javax.persistence.metamodel.SingularAttribute; +import java.util.List; +import java.util.Set; + +/** + * A modified {@link BlazeCriteriaBuilder} for CTE's. + * + * @param the entity type that is the entity of the cTE + * @author Jan-Willem Gmelig Meyling + * @since 1.4.0 + */ +public interface BlazeCTECriteria extends BlazeAbstractQuery { + + /** + * Like {@link BlazeCTECriteria#getRoots()} but returns the subtype {@link BlazeRoot} instead. + * + * @return the set of query roots + */ + Set> getBlazeRoots(); + + Set> getRoots(); + + /** + * Update the value of the specified attribute. + * + * @param attribute attribute to be updated + * @param value new value + * + * @return the modified query + */ + BlazeCTECriteria set(SingularAttribute attribute, X value); + + /** + * Update the value of the specified attribute. + * + * @param attribute attribute to be updated + * @param value new value + * + * @return the modified query + */ + BlazeCTECriteria set(SingularAttribute attribute, Expression value); + + /** + * Update the value of the specified attribute. + * + * @param attribute attribute to be updated + * @param value new value + * + * @return the modified query + */ + BlazeCTECriteria set(Path attribute, X value); + + /** + * Update the value of the specified attribute. + * + * @param attribute attribute to be updated + * @param value new value + * + * @return the modified query + */ + BlazeCTECriteria set(Path attribute, Expression value); + + /** + * Update the value of the specified attribute. + * + * @param attributeName name of the attribute to be updated + * @param value new value + * + * @return the modified query + */ + BlazeCTECriteria set(String attributeName, Object value); + + @Override + BlazeCTECriteria where(Expression restriction); + + @Override + BlazeCTECriteria where(Predicate... restrictions); + + @Override + BlazeCTECriteria groupBy(Expression... grouping); + + @Override + BlazeCTECriteria groupBy(List> grouping); + + @Override + BlazeCTECriteria having(Expression restriction); + + @Override + BlazeCTECriteria having(Predicate... restrictions); + + @Override + BlazeCTECriteria distinct(boolean distinct); + +} diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java new file mode 100644 index 0000000000..b76fc3bac8 --- /dev/null +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java @@ -0,0 +1,194 @@ +package com.blazebit.persistence.criteria.impl; + +import com.blazebit.persistence.criteria.BlazeCTECriteria; +import com.blazebit.persistence.criteria.BlazeCriteriaQuery; +import com.blazebit.persistence.criteria.BlazeRoot; +import com.blazebit.persistence.criteria.BlazeSubquery; + +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import javax.persistence.criteria.Selection; +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.SingularAttribute; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class BlazeCTECriteriaImpl implements BlazeCTECriteria { + + private final BlazeCriteriaBuilderImpl criteriaBuilder; + + private final Class returnType; + private final InternalQuery query; + + public BlazeCTECriteriaImpl(BlazeCriteriaBuilderImpl criteriaBuilder, Class returnType) { + this.criteriaBuilder = criteriaBuilder; + this.returnType = returnType; + this.query = new InternalQuery(this, criteriaBuilder); + } + + @Override + public BlazeCTECriteria with(Class clasz) { + return null; + } + + @Override + public Set> getRoots() { + return query.getRoots(); + } + + @Override + public Set> getBlazeRoots() { + return query.getBlazeRoots(); + } + + @Override + public BlazeRoot from(Class entityClass) { + return query.from(entityClass, null); + } + + @Override + public BlazeRoot from(EntityType entityType) { + return query.from(entityType, null); + } + + @Override + public BlazeRoot from(Class entityClass, String alias) { + return query.from(entityClass, alias); + } + + @Override + public BlazeRoot from(EntityType entityType, String alias) { + return query.from(entityType, alias); + } + + @Override + public Selection getSelection() { + return query.getSelection(); + } + + @Override + public Class getResultType() { + return returnType; + } + + @Override + public BlazeCTECriteria set(SingularAttribute attribute, X value) { + return null; + } + + @Override + public BlazeCTECriteria set(SingularAttribute attribute, Expression value) { + return null; + } + + @Override + public BlazeCTECriteria set(Path attribute, X value) { + return null; + } + + @Override + public BlazeCTECriteria set(Path attribute, Expression value) { + return null; + } + + @Override + public BlazeCTECriteria set(String attributeName, Object value) { + return null; + } + + /* Where */ + + @Override + public Predicate getRestriction() { + return query.getRestriction(); + } + + @Override + public BlazeCTECriteriaImpl where(Expression restriction) { + query.setRestriction(restriction == null ? null : criteriaBuilder.wrap(restriction)); + return this; + } + + @Override + public BlazeCTECriteria where(Predicate... restrictions) { + if (restrictions == null || restrictions.length == 0) { + query.setRestriction(null); + } else { + query.setRestriction(criteriaBuilder.and(restrictions)); + } + return this; + } + + /* Group by */ + + @Override + public List> getGroupList() { + return query.getGroupList(); + } + + @Override + @SuppressWarnings("unchecked") + public BlazeCTECriteria groupBy(Expression... groupings) { + if (groupings == null || groupings.length == 0) { + query.setGroupList(Collections.EMPTY_LIST); + } else { + query.setGroupList(Arrays.asList(groupings)); + } + + return this; + } + + @Override + public BlazeCTECriteria groupBy(List> groupings) { + query.setGroupList(groupings); + return this; + } + + /* Having */ + + @Override + public Predicate getGroupRestriction() { + return query.getGroupRestriction(); + } + + @Override + public BlazeCTECriteria having(Expression restriction) { + if (restriction == null) { + query.setHaving(null); + } else { + query.setHaving(criteriaBuilder.wrap(restriction)); + } + return this; + } + + @Override + public BlazeCTECriteria having(Predicate... restrictions) { + if (restrictions == null || restrictions.length == 0) { + query.setHaving(null); + } else { + query.setHaving(criteriaBuilder.and(restrictions)); + } + return this; + } + + @Override + public boolean isDistinct() { + return query.isDistinct(); + } + + @Override + public BlazeCTECriteria distinct(boolean distinct) { + query.setDistinct(distinct); + return this; + } + + @Override + public BlazeSubquery subquery(Class type) { + return query.subquery(type); + } + +} diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCriteriaQueryImpl.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCriteriaQueryImpl.java index 93921ce0ef..7adaafc576 100644 --- a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCriteriaQueryImpl.java +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCriteriaQueryImpl.java @@ -17,6 +17,7 @@ package com.blazebit.persistence.criteria.impl; import com.blazebit.persistence.CriteriaBuilder; +import com.blazebit.persistence.criteria.BlazeCTECriteria; import com.blazebit.persistence.criteria.BlazeCriteriaBuilder; import com.blazebit.persistence.criteria.BlazeCriteriaQuery; import com.blazebit.persistence.criteria.BlazeOrder; @@ -280,4 +281,9 @@ public CriteriaBuilder createCriteriaBuilder(EntityManager entityManager) { return query.render(cb); } + @Override + public BlazeCTECriteria with(Class clasz) { + + return null; + } } From e296367431cd07f3b9dc8e84cf1383fbbe6305e2 Mon Sep 17 00:00:00 2001 From: Jan-Willem Gmelig Meyling Date: Fri, 27 Dec 2019 00:26:11 +0100 Subject: [PATCH 2/6] General interface structrure --- .../criteria/BlazeCTECriteria.java | 26 ++ .../criteria/impl/BlazeCTECriteriaImpl.java | 244 +++++++++++++++--- .../persistence/criteria/CteTest.java | 104 ++++++++ 3 files changed, 337 insertions(+), 37 deletions(-) create mode 100644 jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java diff --git a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java index 10592a9c66..800a6a2852 100644 --- a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java +++ b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java @@ -16,10 +16,14 @@ package com.blazebit.persistence.criteria; +import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Order; +import javax.persistence.criteria.ParameterExpression; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import javax.persistence.metamodel.Bindable; import javax.persistence.metamodel.SingularAttribute; import java.util.List; import java.util.Set; @@ -40,6 +44,14 @@ public interface BlazeCTECriteria extends BlazeAbstractQuery { */ Set> getBlazeRoots(); + /** + * Like {@link CriteriaQuery#getOrderList()} but returns the subtype {@link BlazeOrder} instead. + * + * @return The list of ordering expressions + */ + List getBlazeOrderList(); + + Set> getRoots(); /** @@ -110,7 +122,21 @@ public interface BlazeCTECriteria extends BlazeAbstractQuery { @Override BlazeCTECriteria having(Predicate... restrictions); + BlazeCTECriteria orderBy(Order... o); + + BlazeCTECriteria orderBy(List o); + @Override BlazeCTECriteria distinct(boolean distinct); + Set> getParameters(); + + // Path-like, except for expression stuff and plural attributes are irrelevant + + Bindable getModel(); + + Path get(SingularAttribute attribute); + + Path get(String attributeName); + } diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java index b76fc3bac8..eff698cf5d 100644 --- a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java @@ -1,33 +1,72 @@ +/* + * Copyright 2014 - 2019 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.blazebit.persistence.criteria.impl; import com.blazebit.persistence.criteria.BlazeCTECriteria; -import com.blazebit.persistence.criteria.BlazeCriteriaQuery; +import com.blazebit.persistence.criteria.BlazeOrder; import com.blazebit.persistence.criteria.BlazeRoot; import com.blazebit.persistence.criteria.BlazeSubquery; +import com.blazebit.persistence.criteria.impl.expression.AbstractExpression; +import com.blazebit.persistence.criteria.impl.expression.SubqueryExpression; +import com.blazebit.persistence.criteria.impl.path.AbstractFrom; +import com.blazebit.persistence.criteria.impl.path.AbstractPath; +import com.blazebit.persistence.criteria.impl.path.RootImpl; +import com.blazebit.persistence.criteria.impl.path.SingularAttributePath; import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Order; +import javax.persistence.criteria.ParameterExpression; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.persistence.criteria.Selection; +import javax.persistence.criteria.Subquery; +import javax.persistence.metamodel.Bindable; import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.SingularAttribute; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; public class BlazeCTECriteriaImpl implements BlazeCTECriteria { - private final BlazeCriteriaBuilderImpl criteriaBuilder; - private final Class returnType; - private final InternalQuery query; + private final BlazeCriteriaBuilderImpl criteriaBuilder; + private final List assignments = new ArrayList(); + private final Root path; + + private boolean distinct; + private Selection selection; + private final Set> roots = new LinkedHashSet<>(); + private Set> correlationRoots; + private Predicate restriction; + private List> groupList = Collections.emptyList(); + private Predicate having; + private List orderList = Collections.emptyList(); + private List> subqueries; public BlazeCTECriteriaImpl(BlazeCriteriaBuilderImpl criteriaBuilder, Class returnType) { - this.criteriaBuilder = criteriaBuilder; this.returnType = returnType; - this.query = new InternalQuery(this, criteriaBuilder); + this.criteriaBuilder = criteriaBuilder; + EntityType entityType = criteriaBuilder.getEntityMetamodel().entity(returnType); + this.path = new RootImpl(criteriaBuilder, entityType, null, false); } @Override @@ -36,38 +75,46 @@ public BlazeCTECriteria with(Class clasz) { } @Override + @SuppressWarnings("unchecked") public Set> getRoots() { - return query.getRoots(); + return (Set>) (Set) roots; } @Override + @SuppressWarnings("unchecked") public Set> getBlazeRoots() { - return query.getBlazeRoots(); - } + return (Set>) (Set) roots; } @Override public BlazeRoot from(Class entityClass) { - return query.from(entityClass, null); + return from(entityClass, null); } @Override public BlazeRoot from(EntityType entityType) { - return query.from(entityType, null); + return from(entityType, null); } @Override public BlazeRoot from(Class entityClass, String alias) { - return query.from(entityClass, alias); + EntityType entityType = criteriaBuilder.getEntityMetamodel().entity(entityClass); + if (entityType == null) { + throw new IllegalArgumentException(entityClass + " is not an entity"); + } + return from(entityType, alias); } @Override public BlazeRoot from(EntityType entityType, String alias) { - return query.from(entityType, alias); + RootImpl root = new RootImpl(criteriaBuilder, entityType, alias, true); + roots.add(root); + return root; } @Override + @SuppressWarnings("unchecked") public Selection getSelection() { - return query.getSelection(); + return (Selection) selection; } @Override @@ -75,50 +122,80 @@ public Class getResultType() { return returnType; } + @Override + public BlazeCTECriteria set(String attributeName, Object value) { + final Path attributePath = path.get(attributeName); + return internalSet(attributePath, valueExpression(attributePath, value)); + } + @Override public BlazeCTECriteria set(SingularAttribute attribute, X value) { - return null; + Path attributePath = path.get(attribute); + return internalSet(attributePath, valueExpression(attributePath, value)); } @Override public BlazeCTECriteria set(SingularAttribute attribute, Expression value) { - return null; + Path attributePath = path.get(attribute); + return internalSet(attributePath, value); } @Override public BlazeCTECriteria set(Path attribute, X value) { - return null; + return internalSet(attribute, valueExpression(attribute, value)); } @Override public BlazeCTECriteria set(Path attribute, Expression value) { - return null; + return internalSet(attribute, value); } - @Override - public BlazeCTECriteria set(String attributeName, Object value) { - return null; + private BlazeCTECriteria internalSet(Path attribute, Expression value) { + if (!(attribute instanceof AbstractPath)) { + throw new IllegalArgumentException("Illegal custom attribute path: " + attribute.getClass().getName()); + } + if (!(attribute instanceof SingularAttributePath)) { + throw new IllegalArgumentException("Only singular attributes can be updated"); + } + if (value == null) { + throw new IllegalArgumentException("Illegal null expression passed. Check your set-call, you probably wanted to pass a literal null"); + } + if (!(value instanceof AbstractExpression)) { + throw new IllegalArgumentException("Illegal custom value expression: " + value.getClass().getName()); + } + assignments.add(new Assignment((SingularAttributePath) attribute, (AbstractExpression) value)); + return this; + } + + private AbstractExpression valueExpression(Path attributePath, Object value) { + if (value == null) { + return criteriaBuilder.nullLiteral(attributePath.getJavaType()); + } else if (value instanceof AbstractExpression) { + return (AbstractExpression) value; + } else { + return criteriaBuilder.literal(value); + } } /* Where */ @Override public Predicate getRestriction() { - return query.getRestriction(); + return restriction; } @Override public BlazeCTECriteriaImpl where(Expression restriction) { - query.setRestriction(restriction == null ? null : criteriaBuilder.wrap(restriction)); + this.restriction = restriction == null ? null : criteriaBuilder.wrap(restriction); return this; } @Override public BlazeCTECriteria where(Predicate... restrictions) { if (restrictions == null || restrictions.length == 0) { - query.setRestriction(null); + this.restriction = null; } else { - query.setRestriction(criteriaBuilder.and(restrictions)); + this.restriction = criteriaBuilder.and(restrictions); } return this; } @@ -127,16 +204,16 @@ public BlazeCTECriteria where(Predicate... restrictions) { @Override public List> getGroupList() { - return query.getGroupList(); + return groupList; } @Override @SuppressWarnings("unchecked") public BlazeCTECriteria groupBy(Expression... groupings) { if (groupings == null || groupings.length == 0) { - query.setGroupList(Collections.EMPTY_LIST); + groupList = Collections.EMPTY_LIST; } else { - query.setGroupList(Arrays.asList(groupings)); + groupList = Arrays.asList(groupings); } return this; @@ -144,7 +221,7 @@ public BlazeCTECriteria groupBy(Expression... groupings) { @Override public BlazeCTECriteria groupBy(List> groupings) { - query.setGroupList(groupings); + groupList = groupings; return this; } @@ -152,15 +229,15 @@ public BlazeCTECriteria groupBy(List> groupings) { @Override public Predicate getGroupRestriction() { - return query.getGroupRestriction(); + return having; } @Override public BlazeCTECriteria having(Expression restriction) { if (restriction == null) { - query.setHaving(null); + having = null; } else { - query.setHaving(criteriaBuilder.wrap(restriction)); + having = criteriaBuilder.wrap(restriction); } return this; } @@ -168,27 +245,120 @@ public BlazeCTECriteria having(Expression restriction) { @Override public BlazeCTECriteria having(Predicate... restrictions) { if (restrictions == null || restrictions.length == 0) { - query.setHaving(null); + having = null; } else { - query.setHaving(criteriaBuilder.and(restrictions)); + having = criteriaBuilder.and(restrictions); } return this; } @Override public boolean isDistinct() { - return query.isDistinct(); + return distinct; } @Override public BlazeCTECriteria distinct(boolean distinct) { - query.setDistinct(distinct); + this.distinct = distinct; return this; } + public List> internalGetSubqueries() { + if (subqueries == null) { + subqueries = new ArrayList>(); + } + return subqueries; + } + @Override public BlazeSubquery subquery(Class type) { - return query.subquery(type); + SubqueryExpression subquery = new SubqueryExpression(criteriaBuilder, type, this); + internalGetSubqueries().add(subquery); + return subquery; } + @Override + public List getBlazeOrderList() { + return orderList; + } + + @Override + public BlazeCTECriteria orderBy(Order... orders) { + if (orders == null || orders.length == 0) { + orderList = Collections.EMPTY_LIST; + } else { + orderList = (List) (List) Arrays.asList(orders); + } + + return this; + } + + @Override + public BlazeCTECriteria orderBy(List orderList) { + this.orderList = (List) (List) orderList; + return null; + } + + @Override + public Set> getParameters() { + // NOTE: we have to always visit them because it's not possible to cache that easily + ParameterVisitor visitor = new ParameterVisitor(); + + visitor.visit(selection); + visitor.visit(restriction); + if (subqueries != null) { + for (Subquery subquery : subqueries) { + visitor.visit(subquery); + } + } + + for (RootImpl r : roots) { + r.visit(visitor); + } + for (AbstractFrom r : correlationRoots) { + r.visit(visitor); + } + + visitor.visit(having); + if (groupList != null) { + for (Expression grouping : groupList) { + visitor.visit(grouping); + } + } + if (orderList != null) { + for (Order ordering : orderList) { + visitor.visit(ordering.getExpression()); + } + } + + return visitor.getParameters(); + } + + @Override + public Bindable getModel() { + return path.getModel(); + } + + @Override + public Path get(SingularAttribute attribute) { + return path.get(attribute); + } + + @Override + public Path get(String attributeName) { + return path.get(attributeName); + } + + /** + * @since 1.4.0 + */ + private static final class Assignment { + private final SingularAttributePath attributePath; + private final AbstractExpression valueExpression; + + public Assignment(SingularAttributePath attributePath, AbstractExpression valueExpression) { + this.attributePath = attributePath; + this.valueExpression = valueExpression; + } + } } diff --git a/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java b/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java new file mode 100644 index 0000000000..f16a62d625 --- /dev/null +++ b/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2014 - 2019 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.criteria; + +import com.blazebit.persistence.ConfigurationProperties; +import com.blazebit.persistence.Criteria; +import com.blazebit.persistence.CriteriaBuilder; +import com.blazebit.persistence.CriteriaBuilderFactory; +import com.blazebit.persistence.spi.CriteriaBuilderConfiguration; +import com.blazebit.persistence.testsuite.AbstractCoreTest; +import com.blazebit.persistence.testsuite.entity.Document; +import com.blazebit.persistence.testsuite.entity.Document_; +import com.blazebit.persistence.testsuite.entity.NameObjectContainer2_; +import com.blazebit.persistence.testsuite.entity.NameObject_; +import com.blazebit.persistence.testsuite.entity.Person; +import com.googlecode.catchexception.CatchException; +import org.junit.Before; +import org.junit.Test; + +import javax.persistence.TypedQuery; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.ParameterExpression; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * + * @author Christian Beikov + * @since 1.2.0 + */ +public class CteTest extends AbstractCoreTest { + + private CriteriaBuilderFactory cbfUnoptimized; + + @Before + public void initNonOptimized() { + CriteriaBuilderConfiguration config = Criteria.getDefault(); + config.getProperties().setProperty(ConfigurationProperties.EXPRESSION_OPTIMIZATION, "false"); + cbfUnoptimized = config.createCriteriaBuilderFactory(emf); + } + + @Test + public void singularAttributeWithLiterals() { + BlazeCriteriaQuery cq = BlazeCriteria.get(cbf, Long.class); + + BlazeCTECriteria documentCte = cq.with(Document.class); + documentCte.set(Document_.name, ""); + documentCte.set(documentCte.get(Document_.nameContainer).get(NameObjectContainer2_.name), ""); + + + BlazeCriteriaBuilder cb = cq.getCriteriaBuilder(); + Root root = cq.from(Document.class, "document"); + + Long longValue = 999999999L; + Path doublePath = root.get(Document_.someValue); + Path integerPath = root.get(Document_.idx); + + cq.select(root.get(Document_.id)); + cq.where(cb.and( + cb.equal(root.get(Document_.id), 1L), + cb.greaterThan(root.get(Document_.creationDate), Calendar.getInstance()), + cb.notEqual(root.get(Document_.lastModified), new Date()), + cb.equal(cb.lower(cb.literal("ABC")), "abc"), + cb.ge( + cb.quot( integerPath, doublePath ), + longValue + ) + )); + + CriteriaBuilder criteriaBuilder = cq.createCriteriaBuilder(em); + assertEquals("SELECT document.id FROM Document document WHERE document.id = 1L AND document.creationDate > :generated_param_0 " + + "AND document.lastModified <> :generated_param_1 AND LOWER(:generated_param_2) = :generated_param_3 AND document.idx / document.someValue >= 999999999L", criteriaBuilder.getQueryString()); + assertEquals(GregorianCalendar.class, criteriaBuilder.getParameter("generated_param_0").getParameterType()); + assertEquals(Date.class, criteriaBuilder.getParameter("generated_param_1").getParameterType()); + assertEquals(String.class, criteriaBuilder.getParameter("generated_param_2").getParameterType()); + } + +} From 10058e247da5fe1c4a0d7fe02ad7721d6b63f717 Mon Sep 17 00:00:00 2001 From: Jan-Willem Gmelig Meyling Date: Fri, 27 Dec 2019 09:37:48 +0100 Subject: [PATCH 3/6] Rename to bind --- .../criteria/BlazeCTECriteria.java | 20 +++++++++---------- .../criteria/impl/BlazeCTECriteriaImpl.java | 10 +++++----- .../persistence/criteria/CteTest.java | 15 ++------------ 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java index 800a6a2852..0e00cc2892 100644 --- a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java +++ b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java @@ -55,54 +55,54 @@ public interface BlazeCTECriteria extends BlazeAbstractQuery { Set> getRoots(); /** - * Update the value of the specified attribute. + * Bind the value of the specified attribute. * * @param attribute attribute to be updated * @param value new value * * @return the modified query */ - BlazeCTECriteria set(SingularAttribute attribute, X value); + BlazeCTECriteria bind(SingularAttribute attribute, X value); /** - * Update the value of the specified attribute. + * Bind the value of the specified attribute. * * @param attribute attribute to be updated * @param value new value * * @return the modified query */ - BlazeCTECriteria set(SingularAttribute attribute, Expression value); + BlazeCTECriteria bind(SingularAttribute attribute, Expression value); /** - * Update the value of the specified attribute. + * Bind the value of the specified attribute. * * @param attribute attribute to be updated * @param value new value * * @return the modified query */ - BlazeCTECriteria set(Path attribute, X value); + BlazeCTECriteria bind(Path attribute, X value); /** - * Update the value of the specified attribute. + * Bind the value of the specified attribute. * * @param attribute attribute to be updated * @param value new value * * @return the modified query */ - BlazeCTECriteria set(Path attribute, Expression value); + BlazeCTECriteria bind(Path attribute, Expression value); /** - * Update the value of the specified attribute. + * Bind the value of the specified attribute. * * @param attributeName name of the attribute to be updated * @param value new value * * @return the modified query */ - BlazeCTECriteria set(String attributeName, Object value); + BlazeCTECriteria bind(String attributeName, Object value); @Override BlazeCTECriteria where(Expression restriction); diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java index eff698cf5d..20d9b18f46 100644 --- a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java @@ -123,30 +123,30 @@ public Class getResultType() { } @Override - public BlazeCTECriteria set(String attributeName, Object value) { + public BlazeCTECriteria bind(String attributeName, Object value) { final Path attributePath = path.get(attributeName); return internalSet(attributePath, valueExpression(attributePath, value)); } @Override - public BlazeCTECriteria set(SingularAttribute attribute, X value) { + public BlazeCTECriteria bind(SingularAttribute attribute, X value) { Path attributePath = path.get(attribute); return internalSet(attributePath, valueExpression(attributePath, value)); } @Override - public BlazeCTECriteria set(SingularAttribute attribute, Expression value) { + public BlazeCTECriteria bind(SingularAttribute attribute, Expression value) { Path attributePath = path.get(attribute); return internalSet(attributePath, value); } @Override - public BlazeCTECriteria set(Path attribute, X value) { + public BlazeCTECriteria bind(Path attribute, X value) { return internalSet(attribute, valueExpression(attribute, value)); } @Override - public BlazeCTECriteria set(Path attribute, Expression value) { + public BlazeCTECriteria bind(Path attribute, Expression value) { return internalSet(attribute, value); } diff --git a/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java b/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java index f16a62d625..1fbe60438f 100644 --- a/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java +++ b/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java @@ -25,25 +25,14 @@ import com.blazebit.persistence.testsuite.entity.Document; import com.blazebit.persistence.testsuite.entity.Document_; import com.blazebit.persistence.testsuite.entity.NameObjectContainer2_; -import com.blazebit.persistence.testsuite.entity.NameObject_; -import com.blazebit.persistence.testsuite.entity.Person; -import com.googlecode.catchexception.CatchException; import org.junit.Before; import org.junit.Test; -import javax.persistence.TypedQuery; -import javax.persistence.criteria.Expression; -import javax.persistence.criteria.ParameterExpression; import javax.persistence.criteria.Path; -import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; -import java.sql.Timestamp; -import java.util.Arrays; import java.util.Calendar; -import java.util.Collection; import java.util.Date; import java.util.GregorianCalendar; -import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -70,8 +59,8 @@ public void singularAttributeWithLiterals() { BlazeCriteriaQuery cq = BlazeCriteria.get(cbf, Long.class); BlazeCTECriteria documentCte = cq.with(Document.class); - documentCte.set(Document_.name, ""); - documentCte.set(documentCte.get(Document_.nameContainer).get(NameObjectContainer2_.name), ""); + documentCte.bind(Document_.name, ""); + documentCte.bind(documentCte.get(Document_.nameContainer).get(NameObjectContainer2_.name), ""); BlazeCriteriaBuilder cb = cq.getCriteriaBuilder(); From d49de4c201c5f7ce4ee51106a0e4e685392ab65d Mon Sep 17 00:00:00 2001 From: Jan-Willem Gmelig Meyling Date: Fri, 27 Dec 2019 16:40:33 +0100 Subject: [PATCH 4/6] Non-recursive CTE rendering --- .../criteria/impl/BlazeCTECriteriaImpl.java | 437 ++++++++++++++++++ .../criteria/impl/BlazeCriteriaQueryImpl.java | 4 +- .../criteria/impl/InternalQuery.java | 23 + .../impl/expression/SubqueryExpression.java | 6 + .../persistence/criteria/CteTest.java | 75 ++- 5 files changed, 501 insertions(+), 44 deletions(-) diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java index 20d9b18f46..e001ecc578 100644 --- a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java @@ -16,16 +16,36 @@ package com.blazebit.persistence.criteria.impl; +import com.blazebit.persistence.BaseSubqueryBuilder; +import com.blazebit.persistence.CriteriaBuilder; +import com.blazebit.persistence.FromBuilder; +import com.blazebit.persistence.FullQueryBuilder; +import com.blazebit.persistence.FullSelectCTECriteriaBuilder; +import com.blazebit.persistence.GroupByBuilder; +import com.blazebit.persistence.HavingBuilder; +import com.blazebit.persistence.JoinOnBuilder; +import com.blazebit.persistence.JoinType; +import com.blazebit.persistence.MultipleSubqueryInitiator; +import com.blazebit.persistence.OrderByBuilder; +import com.blazebit.persistence.SubqueryBuilder; +import com.blazebit.persistence.SubqueryInitiator; +import com.blazebit.persistence.WhereBuilder; import com.blazebit.persistence.criteria.BlazeCTECriteria; +import com.blazebit.persistence.criteria.BlazeJoin; import com.blazebit.persistence.criteria.BlazeOrder; import com.blazebit.persistence.criteria.BlazeRoot; import com.blazebit.persistence.criteria.BlazeSubquery; import com.blazebit.persistence.criteria.impl.expression.AbstractExpression; +import com.blazebit.persistence.criteria.impl.expression.AbstractSelection; +import com.blazebit.persistence.criteria.impl.expression.LiteralExpression; import com.blazebit.persistence.criteria.impl.expression.SubqueryExpression; import com.blazebit.persistence.criteria.impl.path.AbstractFrom; +import com.blazebit.persistence.criteria.impl.path.AbstractJoin; import com.blazebit.persistence.criteria.impl.path.AbstractPath; +import com.blazebit.persistence.criteria.impl.path.EntityJoin; import com.blazebit.persistence.criteria.impl.path.RootImpl; import com.blazebit.persistence.criteria.impl.path.SingularAttributePath; +import com.blazebit.persistence.criteria.impl.path.TreatedPath; import javax.persistence.criteria.Expression; import javax.persistence.criteria.Order; @@ -35,14 +55,17 @@ import javax.persistence.criteria.Root; import javax.persistence.criteria.Selection; import javax.persistence.criteria.Subquery; +import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.Bindable; import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.SingularAttribute; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; public class BlazeCTECriteriaImpl implements BlazeCTECriteria { @@ -361,4 +384,418 @@ public Assignment(SingularAttributePath attributePath, AbstractExpression this.valueExpression = valueExpression; } } + + public CriteriaBuilder render(CriteriaBuilder cbs) { + + FullSelectCTECriteriaBuilder> fullSelectCTECriteriaBuilder = cbs.with(returnType); + RenderContextImpl context = new RenderContextImpl(); + + + renderFrom(fullSelectCTECriteriaBuilder, context); + context.setClauseType(RenderContext.ClauseType.SET); + for (Assignment a : assignments) { + // TODO apply this for AbstractModificationCriteriaQuery as well + String attribute = a.attributePath.getPathExpression().substring(String.valueOf(path.getAlias()).length() + 1); + + if (a.valueExpression instanceof LiteralExpression) { + Object value = ((LiteralExpression) a.valueExpression).getLiteral(); + String exp = context.registerLiteralParameterBinding(value, value.getClass()); + fullSelectCTECriteriaBuilder.bind(attribute).select(":" + exp); + } else { + context.getBuffer().setLength(0); + a.valueExpression.render(context); + String valueExpression = context.takeBuffer(); + Map> aliasToSubqueries = context.takeAliasToSubqueryMap(); + + if (aliasToSubqueries.isEmpty()) { + fullSelectCTECriteriaBuilder.bind(attribute).select(valueExpression); + } else { + MultipleSubqueryInitiator>> initiator = fullSelectCTECriteriaBuilder.bind(attribute).selectSubqueries(valueExpression); + + for (Map.Entry> subqueryEntry : aliasToSubqueries.entrySet()) { + context.pushSubqueryInitiator(initiator.with(subqueryEntry.getKey())); + subqueryEntry.getValue().renderSubquery(context); + context.popSubqueryInitiator(); + } + + initiator.end(); + } + } + } + + renderWhere(fullSelectCTECriteriaBuilder, context); + renderGroupBy(fullSelectCTECriteriaBuilder, context); + renderHaving(fullSelectCTECriteriaBuilder, context); + renderOrderBy(fullSelectCTECriteriaBuilder, context); + + for (ImplicitParameterBinding b : context.getImplicitParameterBindings()) { + b.bind(fullSelectCTECriteriaBuilder); + } + + for (Map.Entry> entry : context.getExplicitParameterNameMapping().entrySet()) { + fullSelectCTECriteriaBuilder.setParameterType(entry.getKey(), entry.getValue().getParameterType()); + } + + return fullSelectCTECriteriaBuilder.end(); + } + + + private void renderFrom(FromBuilder cb, RenderContextImpl context) { + context.setClauseType(RenderContext.ClauseType.FROM); + + for (BlazeRoot r : roots) { + ((AbstractFrom) r).prepareAlias(context); + if (r.getAlias() != null) { + cb.from(r.getModel(), r.getAlias()); + } else { + cb.from(r.getModel()); + } + } + + for (RootImpl r : roots) { + renderJoins(cb, context, r, true); + } + } + + @SuppressWarnings("unchecked") + private void renderJoins(FromBuilder cb, RenderContextImpl context, AbstractFrom r, boolean fetching) { + String path; + if (r.getAlias() != null) { + path = r.getAlias(); + } else { + path = ""; + } + + renderJoins(cb, null, true, context, path, (Set>) (Set) r.getBlazeJoins()); + Collection> treatedPaths = (Collection>) (Collection) r.getTreatedPaths(); + if (treatedPaths != null && treatedPaths.size() > 0) { + for (TreatedPath treatedPath : treatedPaths) { + RootImpl treatedRoot = (RootImpl) treatedPath; + String treatedParentPath = "TREAT(" + path + " AS " + treatedPath.getTreatType().getName() + ')'; + renderJoins(cb, null, fetching, context, treatedParentPath, (Set>) (Set) treatedRoot.getBlazeJoins()); + } + } + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private SubqueryBuilder renderSubqueryFrom(SubqueryInitiator initiator, RenderContextImpl context) { + SubqueryBuilder cb = null; + context.setClauseType(RenderContext.ClauseType.FROM); + + for (RootImpl r : roots) { + r.prepareAlias(context); + if (cb == null) { + if (r.getAlias() != null) { + cb = initiator.from(r.getJavaType(), r.getAlias()); + } else { + cb = initiator.from(r.getJavaType()); + } + } else { + if (r.getAlias() != null) { + cb.from(r.getJavaType(), r.getAlias()); + } else { + cb.from(r.getJavaType()); + } + } + } + + if (correlationRoots != null) { + for (AbstractFrom r : correlationRoots) { + r.prepareAlias(context); + Set> joins = (Set>) (Set) r.getBlazeJoins(); + + for (BlazeJoin j : joins) { + AbstractJoin join = (AbstractJoin) j; + join.prepareAlias(context); + EntityType treatJoinType = join.getTreatJoinType(); + String path = getPath(r.getAlias(), j, treatJoinType); + if (j.getAttribute() != null && j.getAttribute().getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED) { + cb = (SubqueryBuilder) renderJoins(cb, initiator, false, context, path, (Set>) (Set) j.getBlazeJoins()); + } else { + if (cb == null) { + if (j.getAlias() != null) { + cb = initiator.from(path, j.getAlias()); + } else { + cb = initiator.from(path); + } + } else { + if (j.getAlias() != null) { + cb.from(path, j.getAlias()); + } else { + cb.from(path); + } + } + } + } + } + } + + for (RootImpl r : roots) { + renderJoins(cb, context, r, false); + } + + if (correlationRoots != null) { + for (AbstractFrom r : correlationRoots) { + Set> joins = (Set>) (Set) r.getBlazeJoins(); + + for (BlazeJoin j : joins) { + // We already rendered correlation joins for embedded paths + if (j.getAttribute() != null && j.getAttribute().getPersistentAttributeType() != Attribute.PersistentAttributeType.EMBEDDED) { + renderJoins(cb, context, (AbstractFrom) j, false); + } + } + } + } + + return cb; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private FromBuilder renderJoins(FromBuilder cb, SubqueryInitiator subqueryInitiator, boolean fetching, RenderContextImpl context, String parentPath, Set> joins) { + if (joins.isEmpty()) { + return cb; + } + + for (BlazeJoin j : joins) { + AbstractJoin join = (AbstractJoin) j; + EntityType treatJoinType = join.getTreatJoinType(); + join.prepareAlias(context); + // TODO: implicit joins? + String path = getPath(parentPath, j, treatJoinType); + String alias = j.getAlias(); + JoinOnBuilder onBuilder = null; + + // "Join" relations in embeddables + if (j.getAttribute() != null && j.getAttribute().getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED) { + alias = path; + } else { + if (j.getOn() != null) { + if (fetching && j.isFetch()) { + throw new IllegalArgumentException("Fetch joining with on-condition is not allowed!" + j); + } else if (j instanceof EntityJoin) { + onBuilder = cb.joinOn(path, (EntityType) j.getModel(), alias, getJoinType(j.getJoinType())); + } else { + onBuilder = cb.joinOn(path, alias, getJoinType(j.getJoinType())); + } + } else { + if (fetching && j.isFetch()) { + ((FullQueryBuilder) cb).join(path, alias, getJoinType(j.getJoinType()), true); + } else if (j instanceof EntityJoin) { + throw new IllegalArgumentException("Entity join without on-condition is not allowed! " + j); + } else if (cb == null) { + cb = subqueryInitiator.from(path, alias); + } else if (cb instanceof BaseSubqueryBuilder && j.isCorrelated()) { + ((SubqueryBuilder) cb).from(path, alias); + } else { + cb.join(path, alias, getJoinType(j.getJoinType())); + } + } + } + + if (onBuilder != null) { + context.setClauseType(RenderContext.ClauseType.ON); + context.getBuffer().setLength(0); + ((AbstractSelection) j.getOn()).render(context); + String expression = context.takeBuffer(); + Map> aliasToSubqueries = context.takeAliasToSubqueryMap(); + + if (aliasToSubqueries.isEmpty()) { + onBuilder.setOnExpression(expression); + } else { + MultipleSubqueryInitiator initiator = onBuilder.setOnExpressionSubqueries(expression); + + for (Map.Entry> subqueryEntry : aliasToSubqueries.entrySet()) { + context.pushSubqueryInitiator(initiator.with(subqueryEntry.getKey())); + subqueryEntry.getValue().renderSubquery(context); + context.popSubqueryInitiator(); + } + + initiator.end(); + } + } + + renderJoins(cb, null, fetching, context, alias, (Set>) (Set) j.getBlazeJoins()); + + Collection> treatedPaths = (Collection>) (Collection) join.getTreatedPaths(); + if (treatedPaths != null && treatedPaths.size() > 0) { + for (TreatedPath treatedPath : treatedPaths) { + AbstractJoin treatedJoin = (AbstractJoin) treatedPath; + String treatedParentPath = "TREAT(" + alias + " AS " + treatedPath.getTreatType().getName() + ')'; + renderJoins(cb, null, fetching, context, treatedParentPath, (Set>) (Set) treatedJoin.getBlazeJoins()); + } + } + } + + return cb; + } + + private String getPath(String parentPath, BlazeJoin j, EntityType treatJoinType) { + if (j.getAttribute() == null) { + return parentPath; + } + String path = j.getAttribute().getName(); + if (parentPath == null || parentPath.isEmpty()) { + if (treatJoinType != null) { + return "TREAT(" + path + " AS " + treatJoinType.getName() + ')'; + } else { + return path; + } + } + + if (treatJoinType != null) { + return "TREAT(" + parentPath + "." + path + " AS " + treatJoinType.getName() + ')'; + } else { + return parentPath + "." + path; + } + } + + private JoinType getJoinType(javax.persistence.criteria.JoinType joinType) { + switch (joinType) { + case INNER: + return JoinType.INNER; + case LEFT: + return JoinType.LEFT; + case RIGHT: + return JoinType.RIGHT; + default: + throw new IllegalArgumentException("Unsupported join type: " + joinType); + } + } + + + protected void renderWhere(WhereBuilder wb, RenderContextImpl context) { + if (restriction == null) { + return; + } + + context.setClauseType(RenderContext.ClauseType.WHERE); + context.getBuffer().setLength(0); + ((AbstractSelection) restriction).render(context); + String expression = context.takeBuffer(); + Map> aliasToSubqueries = context.takeAliasToSubqueryMap(); + + if (aliasToSubqueries.isEmpty()) { + wb.setWhereExpression(expression); + } else { + MultipleSubqueryInitiator initiator = wb.setWhereExpressionSubqueries(expression); + + for (Map.Entry> subqueryEntry : aliasToSubqueries.entrySet()) { + context.pushSubqueryInitiator(initiator.with(subqueryEntry.getKey())); + subqueryEntry.getValue().renderSubquery(context); + context.popSubqueryInitiator(); + } + + initiator.end(); + } + } + + + private void renderTreatTypeRestrictions(RenderContextImpl context, List> treatedSelections) { + final StringBuilder buffer = context.getBuffer(); + boolean first = buffer.length() == 0; + + for (TreatedPath p : treatedSelections) { + if (first) { + first = false; + } else { + buffer.append(" AND "); + } + + buffer.append("TYPE(") + .append(p.getAlias()) + .append(") = ") + .append(p.getTreatType().getName()); + } + } + + private void renderGroupBy(GroupByBuilder gb, RenderContextImpl context) { + if (groupList == null) { + return; + } + + context.setClauseType(RenderContext.ClauseType.GROUP_BY); + for (Expression expr : groupList) { + context.getBuffer().setLength(0); + ((AbstractSelection) expr).render(context); + String expression = context.takeBuffer(); + Map> aliasToSubqueries = context.takeAliasToSubqueryMap(); + + if (aliasToSubqueries.isEmpty()) { + gb.groupBy(expression); + } else { + throw new IllegalArgumentException("Subqueries are not supported in the group by clause!"); + // MultipleSubqueryInitiator initiator = gb.groupBySubqueries(expression); + // + // for (Map.Entry> subqueryEntry : aliasToSubqueries.entrySet()) { + // context.pushSubqueryInitiator(initiator.with(subqueryEntry.getKey())); + // subqueryEntry.getValue().renderSubquery(context); + // context.popSubqueryInitiator(); + // } + // + // initiator.end(); + } + } + } + + private void renderHaving(HavingBuilder hb, RenderContextImpl context) { + if (having == null) { + return; + } + + context.setClauseType(RenderContext.ClauseType.HAVING); + context.getBuffer().setLength(0); + ((AbstractSelection) having).render(context); + String expression = context.takeBuffer(); + Map> aliasToSubqueries = context.takeAliasToSubqueryMap(); + + if (aliasToSubqueries.isEmpty()) { + hb.setHavingExpression(expression); + } else { + MultipleSubqueryInitiator initiator = hb.setHavingExpressionSubqueries(expression); + + for (Map.Entry> subqueryEntry : aliasToSubqueries.entrySet()) { + context.pushSubqueryInitiator(initiator.with(subqueryEntry.getKey())); + subqueryEntry.getValue().renderSubquery(context); + context.popSubqueryInitiator(); + } + + initiator.end(); + } + } + + private void renderOrderBy(OrderByBuilder ob, RenderContextImpl context) { + if (orderList == null) { + return; + } + + context.setClauseType(RenderContext.ClauseType.ORDER_BY); + for (Order order : orderList) { + context.getBuffer().setLength(0); + ((AbstractSelection) order.getExpression()).render(context); + String expression = context.takeBuffer(); + Map> aliasToSubqueries = context.takeAliasToSubqueryMap(); + + if (aliasToSubqueries.isEmpty()) { + boolean nullsFirst = false; + + if (order instanceof BlazeOrder) { + nullsFirst = ((BlazeOrder) order).isNullsFirst(); + } + + ob.orderBy(expression, order.isAscending(), nullsFirst); + } else { + throw new IllegalArgumentException("Subqueries are not supported in the order by clause!"); + // MultipleSubqueryInitiator initiator = ob.groupBySubqueries(expression); + // + // for (Map.Entry> subqueryEntry : aliasToSubqueries.entrySet()) { + // context.pushSubqueryInitiator(initiator.with(subqueryEntry.getKey())); + // subqueryEntry.getValue().renderSubquery(context); + // context.popSubqueryInitiator(); + // } + // + // initiator.end(); + } + } + } + } diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCriteriaQueryImpl.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCriteriaQueryImpl.java index 7adaafc576..1ba55b9285 100644 --- a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCriteriaQueryImpl.java +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCriteriaQueryImpl.java @@ -283,7 +283,7 @@ public CriteriaBuilder createCriteriaBuilder(EntityManager entityManager) { @Override public BlazeCTECriteria with(Class clasz) { - - return null; + return query.with(clasz); } + } diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/InternalQuery.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/InternalQuery.java index f43c22e8e7..961605aa74 100644 --- a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/InternalQuery.java +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/InternalQuery.java @@ -32,6 +32,7 @@ import com.blazebit.persistence.SubqueryInitiator; import com.blazebit.persistence.WhereBuilder; import com.blazebit.persistence.criteria.BlazeAbstractQuery; +import com.blazebit.persistence.criteria.BlazeCTECriteria; import com.blazebit.persistence.criteria.BlazeJoin; import com.blazebit.persistence.criteria.BlazeOrder; import com.blazebit.persistence.criteria.BlazeRoot; @@ -277,6 +278,13 @@ public CriteriaBuilder render(CriteriaBuilder cb) { } RenderContextImpl context = new RenderContextImpl(); + + if (ctes != null && ctes.size() > 0) { + for (BlazeCTECriteriaImpl cte : ctes) { + cte.render(cb); + } + } + renderFrom(cb, context); List> treatedSelections = renderSelect(cb, context); @@ -805,4 +813,19 @@ private void renderOrderBy(OrderByBuilder ob, RenderContextImpl context) { } } + private List> ctes; + + private List> getCtesInternal() { + if (ctes == null) { + ctes = new ArrayList<>(); + } + return ctes; + } + + public BlazeCTECriteria with(Class clasz) { + BlazeCTECriteriaImpl cteCriteria = new BlazeCTECriteriaImpl<>(criteriaBuilder, clasz); + getCtesInternal().add(cteCriteria); + return cteCriteria; + } + } diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/expression/SubqueryExpression.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/expression/SubqueryExpression.java index bd3ad59e20..34379f6871 100644 --- a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/expression/SubqueryExpression.java +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/expression/SubqueryExpression.java @@ -17,6 +17,7 @@ package com.blazebit.persistence.criteria.impl.expression; import com.blazebit.persistence.criteria.BlazeAbstractQuery; +import com.blazebit.persistence.criteria.BlazeCTECriteria; import com.blazebit.persistence.criteria.BlazeCollectionJoin; import com.blazebit.persistence.criteria.BlazeCommonAbstractCriteria; import com.blazebit.persistence.criteria.BlazeJoin; @@ -72,6 +73,11 @@ public SubqueryExpression(BlazeCriteriaBuilderImpl criteriaBuilder, Class jav this.query = new InternalQuery(this, criteriaBuilder); } + @Override + public BlazeCTECriteria with(Class clasz) { + return null; + } + @Override @SuppressWarnings({"unchecked"}) public BlazeAbstractQuery getParent() { diff --git a/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java b/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java index 1fbe60438f..b6ac1a032f 100644 --- a/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java +++ b/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java @@ -22,26 +22,21 @@ import com.blazebit.persistence.CriteriaBuilderFactory; import com.blazebit.persistence.spi.CriteriaBuilderConfiguration; import com.blazebit.persistence.testsuite.AbstractCoreTest; -import com.blazebit.persistence.testsuite.entity.Document; -import com.blazebit.persistence.testsuite.entity.Document_; -import com.blazebit.persistence.testsuite.entity.NameObjectContainer2_; +import com.blazebit.persistence.testsuite.entity.RecursiveEntity; +import com.blazebit.persistence.testsuite.entity.RecursiveEntity_; +import com.blazebit.persistence.testsuite.entity.TestAdvancedCTE1; +import com.blazebit.persistence.testsuite.entity.TestAdvancedCTE1_; +import com.blazebit.persistence.testsuite.entity.TestAdvancedCTE2; +import com.blazebit.persistence.testsuite.entity.TestCTE; +import com.blazebit.persistence.testsuite.entity.TestCTEEmbeddable_; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import javax.persistence.criteria.Path; -import javax.persistence.criteria.Root; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - /** * - * @author Christian Beikov - * @since 1.2.0 + * @author Jan-Willem Gmelig Meyling + * @since 1.4.0 */ public class CteTest extends AbstractCoreTest { @@ -54,40 +49,36 @@ public void initNonOptimized() { cbfUnoptimized = config.createCriteriaBuilderFactory(emf); } - @Test - public void singularAttributeWithLiterals() { - BlazeCriteriaQuery cq = BlazeCriteria.get(cbf, Long.class); - - BlazeCTECriteria documentCte = cq.with(Document.class); - documentCte.bind(Document_.name, ""); - documentCte.bind(documentCte.get(Document_.nameContainer).get(NameObjectContainer2_.name), ""); + @Override + protected Class[] getEntityClasses() { + return new Class[] { + RecursiveEntity.class, + TestCTE.class, + TestAdvancedCTE1.class, + TestAdvancedCTE2.class + }; + } + @Test + public void testBindEmbeddable() { + BlazeCriteriaQuery cq = BlazeCriteria.get(cbf, TestAdvancedCTE1.class); BlazeCriteriaBuilder cb = cq.getCriteriaBuilder(); - Root root = cq.from(Document.class, "document"); - Long longValue = 999999999L; - Path doublePath = root.get(Document_.someValue); - Path integerPath = root.get(Document_.idx); + BlazeCTECriteria documentCte = cq.with(TestAdvancedCTE1.class); + BlazeRoot recursiveEntity = documentCte.from(RecursiveEntity.class); + documentCte.bind(TestAdvancedCTE1_.id, recursiveEntity.get(RecursiveEntity_.id)); + documentCte.bind(documentCte.get(TestAdvancedCTE1_.embeddable).get(TestCTEEmbeddable_.name), recursiveEntity.get(RecursiveEntity_.name)); + documentCte.bind(documentCte.get(TestAdvancedCTE1_.embeddable).get(TestCTEEmbeddable_.description), "desc"); + documentCte.bind(documentCte.get(TestAdvancedCTE1_.embeddable).get(TestCTEEmbeddable_.recursiveEntity), recursiveEntity); + documentCte.bind(TestAdvancedCTE1_.level, cb.literal(0)); + documentCte.bind(TestAdvancedCTE1_.parent, recursiveEntity.get(RecursiveEntity_.parent)); - cq.select(root.get(Document_.id)); - cq.where(cb.and( - cb.equal(root.get(Document_.id), 1L), - cb.greaterThan(root.get(Document_.creationDate), Calendar.getInstance()), - cb.notEqual(root.get(Document_.lastModified), new Date()), - cb.equal(cb.lower(cb.literal("ABC")), "abc"), - cb.ge( - cb.quot( integerPath, doublePath ), - longValue - ) - )); + cq.from(TestAdvancedCTE1.class); CriteriaBuilder criteriaBuilder = cq.createCriteriaBuilder(em); - assertEquals("SELECT document.id FROM Document document WHERE document.id = 1L AND document.creationDate > :generated_param_0 " + - "AND document.lastModified <> :generated_param_1 AND LOWER(:generated_param_2) = :generated_param_3 AND document.idx / document.someValue >= 999999999L", criteriaBuilder.getQueryString()); - assertEquals(GregorianCalendar.class, criteriaBuilder.getParameter("generated_param_0").getParameterType()); - assertEquals(Date.class, criteriaBuilder.getParameter("generated_param_1").getParameterType()); - assertEquals(String.class, criteriaBuilder.getParameter("generated_param_2").getParameterType()); + String queryString = criteriaBuilder.getQueryString(); + Assert.assertNotNull(queryString); } } From 659ba497c06c907a1f0d1ceeef0d1063e7435a28 Mon Sep 17 00:00:00 2001 From: Jan-Willem Gmelig Meyling Date: Sat, 28 Dec 2019 00:20:03 +0100 Subject: [PATCH 5/6] Simplify parameter rendering --- .../criteria/impl/BlazeCTECriteriaImpl.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java index e001ecc578..cd9be44fea 100644 --- a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java @@ -390,21 +390,25 @@ public CriteriaBuilder render(CriteriaBuilder cbs) { FullSelectCTECriteriaBuilder> fullSelectCTECriteriaBuilder = cbs.with(returnType); RenderContextImpl context = new RenderContextImpl(); - renderFrom(fullSelectCTECriteriaBuilder, context); - context.setClauseType(RenderContext.ClauseType.SET); + renderWhere(fullSelectCTECriteriaBuilder, context); + renderGroupBy(fullSelectCTECriteriaBuilder, context); + renderHaving(fullSelectCTECriteriaBuilder, context); + renderOrderBy(fullSelectCTECriteriaBuilder, context); + + context.setClauseType(RenderContext.ClauseType.SELECT); + for (Assignment a : assignments) { // TODO apply this for AbstractModificationCriteriaQuery as well String attribute = a.attributePath.getPathExpression().substring(String.valueOf(path.getAlias()).length() + 1); + context.getBuffer().setLength(0); + a.valueExpression.render(context); + String valueExpression = context.takeBuffer(); + if (a.valueExpression instanceof LiteralExpression) { - Object value = ((LiteralExpression) a.valueExpression).getLiteral(); - String exp = context.registerLiteralParameterBinding(value, value.getClass()); - fullSelectCTECriteriaBuilder.bind(attribute).select(":" + exp); + fullSelectCTECriteriaBuilder.bind(attribute).select(valueExpression); } else { - context.getBuffer().setLength(0); - a.valueExpression.render(context); - String valueExpression = context.takeBuffer(); Map> aliasToSubqueries = context.takeAliasToSubqueryMap(); if (aliasToSubqueries.isEmpty()) { @@ -423,11 +427,6 @@ public CriteriaBuilder render(CriteriaBuilder cbs) { } } - renderWhere(fullSelectCTECriteriaBuilder, context); - renderGroupBy(fullSelectCTECriteriaBuilder, context); - renderHaving(fullSelectCTECriteriaBuilder, context); - renderOrderBy(fullSelectCTECriteriaBuilder, context); - for (ImplicitParameterBinding b : context.getImplicitParameterBindings()) { b.bind(fullSelectCTECriteriaBuilder); } From 0e5def89b6133f4bde1dc83bcc731c82b9f8ea8a Mon Sep 17 00:00:00 2001 From: Jan-Willem Gmelig Meyling Date: Sat, 28 Dec 2019 02:41:04 +0100 Subject: [PATCH 6/6] Preliminary work on recursive CTEs --- .../criteria/BlazeAbstractQuery.java | 4 +- .../criteria/BlazeBaseCTECriteria.java | 92 ++++++++++++ .../criteria/BlazeCTECriteria.java | 142 ------------------ .../criteria/BlazeFullSelectCTECriteria.java | 26 ++++ .../criteria/BlazeSelectBaseCTECriteria.java | 81 ++++++++++ .../criteria/BlazeSelectCTECriteria.java | 26 ++++ .../BlazeSelectRecursiveCTECriteria.java | 31 ++++ ...> AbstractBlazeSelectBaseCTECriteria.java} | 66 ++++---- .../criteria/impl/BlazeCriteriaQueryImpl.java | 10 +- .../impl/BlazeFullSelectCTECriteriaImpl.java | 27 ++++ .../impl/BlazeSelectCTECriteriaImpl.java | 28 ++++ .../BlazeSelectRecursiveCTECriteriaImpl.java | 60 ++++++++ .../criteria/impl/InternalQuery.java | 17 ++- .../impl/expression/SubqueryExpression.java | 10 +- .../persistence/criteria/CteTest.java | 60 +++++++- 15 files changed, 498 insertions(+), 182 deletions(-) create mode 100644 jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeBaseCTECriteria.java delete mode 100644 jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java create mode 100644 jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeFullSelectCTECriteria.java create mode 100644 jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeSelectBaseCTECriteria.java create mode 100644 jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeSelectCTECriteria.java create mode 100644 jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeSelectRecursiveCTECriteria.java rename jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/{BlazeCTECriteriaImpl.java => AbstractBlazeSelectBaseCTECriteria.java} (91%) create mode 100644 jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeFullSelectCTECriteriaImpl.java create mode 100644 jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeSelectCTECriteriaImpl.java create mode 100644 jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeSelectRecursiveCTECriteriaImpl.java diff --git a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeAbstractQuery.java b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeAbstractQuery.java index 1df1c719d3..9be313ad9b 100644 --- a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeAbstractQuery.java +++ b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeAbstractQuery.java @@ -37,7 +37,9 @@ public interface BlazeAbstractQuery extends AbstractQuery, BlazeCommonAbst // TODO: integrate support for default join nodes? // TODO: maybe add explicit support for limit? - public BlazeCTECriteria with(Class clasz); + public BlazeFullSelectCTECriteria with(Class clasz); + + public BlazeSelectRecursiveCTECriteria withRecursive(Class clasz); /** * Like {@link AbstractQuery#from(Class)} but allows to set the alias of the {@link BlazeRoot}. diff --git a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeBaseCTECriteria.java b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeBaseCTECriteria.java new file mode 100644 index 0000000000..251b658d4d --- /dev/null +++ b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeBaseCTECriteria.java @@ -0,0 +1,92 @@ +/* + * Copyright 2014 - 2019 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.criteria; + +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Order; +import javax.persistence.criteria.ParameterExpression; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import javax.persistence.metamodel.Bindable; +import javax.persistence.metamodel.SingularAttribute; +import java.util.List; +import java.util.Set; + +/** + * + * @param + * @author Jan-Willem Gmelig Meyling + * @since 1.4.0 + */ +public interface BlazeBaseCTECriteria extends BlazeAbstractQuery { + + + /** + * Like {@link BlazeBaseCTECriteria#getRoots()} but returns the subtype {@link BlazeRoot} instead. + * + * @return the set of query roots + */ + Set> getBlazeRoots(); + + /** + * Like {@link CriteriaQuery#getOrderList()} but returns the subtype {@link BlazeOrder} instead. + * + * @return The list of ordering expressions + */ + List getBlazeOrderList(); + + + Set> getRoots(); + + @Override + BlazeBaseCTECriteria where(Expression restriction); + + @Override + BlazeBaseCTECriteria where(Predicate... restrictions); + + @Override + BlazeBaseCTECriteria groupBy(Expression... grouping); + + @Override + BlazeBaseCTECriteria groupBy(List> grouping); + + @Override + BlazeBaseCTECriteria having(Expression restriction); + + @Override + BlazeBaseCTECriteria having(Predicate... restrictions); + + BlazeBaseCTECriteria orderBy(Order... o); + + BlazeBaseCTECriteria orderBy(List o); + + @Override + BlazeBaseCTECriteria distinct(boolean distinct); + + Set> getParameters(); + + // Path-like, except for expression stuff and plural attributes are irrelevant + + Bindable getModel(); + + Path get(SingularAttribute attribute); + + Path get(String attributeName); + +} diff --git a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java deleted file mode 100644 index 0e00cc2892..0000000000 --- a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeCTECriteria.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2014 - 2019 Blazebit. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.blazebit.persistence.criteria; - -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Expression; -import javax.persistence.criteria.Order; -import javax.persistence.criteria.ParameterExpression; -import javax.persistence.criteria.Path; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; -import javax.persistence.metamodel.Bindable; -import javax.persistence.metamodel.SingularAttribute; -import java.util.List; -import java.util.Set; - -/** - * A modified {@link BlazeCriteriaBuilder} for CTE's. - * - * @param the entity type that is the entity of the cTE - * @author Jan-Willem Gmelig Meyling - * @since 1.4.0 - */ -public interface BlazeCTECriteria extends BlazeAbstractQuery { - - /** - * Like {@link BlazeCTECriteria#getRoots()} but returns the subtype {@link BlazeRoot} instead. - * - * @return the set of query roots - */ - Set> getBlazeRoots(); - - /** - * Like {@link CriteriaQuery#getOrderList()} but returns the subtype {@link BlazeOrder} instead. - * - * @return The list of ordering expressions - */ - List getBlazeOrderList(); - - - Set> getRoots(); - - /** - * Bind the value of the specified attribute. - * - * @param attribute attribute to be updated - * @param value new value - * - * @return the modified query - */ - BlazeCTECriteria bind(SingularAttribute attribute, X value); - - /** - * Bind the value of the specified attribute. - * - * @param attribute attribute to be updated - * @param value new value - * - * @return the modified query - */ - BlazeCTECriteria bind(SingularAttribute attribute, Expression value); - - /** - * Bind the value of the specified attribute. - * - * @param attribute attribute to be updated - * @param value new value - * - * @return the modified query - */ - BlazeCTECriteria bind(Path attribute, X value); - - /** - * Bind the value of the specified attribute. - * - * @param attribute attribute to be updated - * @param value new value - * - * @return the modified query - */ - BlazeCTECriteria bind(Path attribute, Expression value); - - /** - * Bind the value of the specified attribute. - * - * @param attributeName name of the attribute to be updated - * @param value new value - * - * @return the modified query - */ - BlazeCTECriteria bind(String attributeName, Object value); - - @Override - BlazeCTECriteria where(Expression restriction); - - @Override - BlazeCTECriteria where(Predicate... restrictions); - - @Override - BlazeCTECriteria groupBy(Expression... grouping); - - @Override - BlazeCTECriteria groupBy(List> grouping); - - @Override - BlazeCTECriteria having(Expression restriction); - - @Override - BlazeCTECriteria having(Predicate... restrictions); - - BlazeCTECriteria orderBy(Order... o); - - BlazeCTECriteria orderBy(List o); - - @Override - BlazeCTECriteria distinct(boolean distinct); - - Set> getParameters(); - - // Path-like, except for expression stuff and plural attributes are irrelevant - - Bindable getModel(); - - Path get(SingularAttribute attribute); - - Path get(String attributeName); - -} diff --git a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeFullSelectCTECriteria.java b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeFullSelectCTECriteria.java new file mode 100644 index 0000000000..4df758be15 --- /dev/null +++ b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeFullSelectCTECriteria.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 - 2019 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.criteria; + +/** + * + * @param + * @author Jan-Willem Gmelig Meyling + * @since 1.4.0 + */ +public interface BlazeFullSelectCTECriteria extends BlazeSelectBaseCTECriteria { +} diff --git a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeSelectBaseCTECriteria.java b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeSelectBaseCTECriteria.java new file mode 100644 index 0000000000..0f15bf7772 --- /dev/null +++ b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeSelectBaseCTECriteria.java @@ -0,0 +1,81 @@ +/* + * Copyright 2014 - 2019 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.criteria; + +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Path; +import javax.persistence.metamodel.SingularAttribute; + +/** + * + * @param + * @author Jan-Willem Gmelig Meyling + * @since 1.4.0 + */ +public interface BlazeSelectBaseCTECriteria extends BlazeBaseCTECriteria { + + /** + * Bind the value of the specified attribute. + * + * @param attribute attribute to be updated + * @param value new value + * + * @return the modified query + */ + BlazeSelectBaseCTECriteria bind(SingularAttribute attribute, X value); + + /** + * Bind the value of the specified attribute. + * + * @param attribute attribute to be updated + * @param value new value + * + * @return the modified query + */ + BlazeSelectBaseCTECriteria bind(SingularAttribute attribute, Expression value); + + /** + * Bind the value of the specified attribute. + * + * @param attribute attribute to be updated + * @param value new value + * + * @return the modified query + */ + BlazeSelectBaseCTECriteria bind(Path attribute, X value); + + /** + * Bind the value of the specified attribute. + * + * @param attribute attribute to be updated + * @param value new value + * + * @return the modified query + */ + BlazeSelectBaseCTECriteria bind(Path attribute, Expression value); + + /** + * Bind the value of the specified attribute. + * + * @param attributeName name of the attribute to be updated + * @param value new value + * + * @return the modified query + */ + BlazeSelectBaseCTECriteria bind(String attributeName, Object value); + +} diff --git a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeSelectCTECriteria.java b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeSelectCTECriteria.java new file mode 100644 index 0000000000..6cc193b1af --- /dev/null +++ b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeSelectCTECriteria.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 - 2019 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.criteria; + +/** + * + * @param + * @author Jan-Willem Gmelig Meyling + * @since 1.4.0 + */ +public interface BlazeSelectCTECriteria extends BlazeSelectBaseCTECriteria { +} diff --git a/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeSelectRecursiveCTECriteria.java b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeSelectRecursiveCTECriteria.java new file mode 100644 index 0000000000..480a3fd794 --- /dev/null +++ b/jpa-criteria/api/src/main/java/com/blazebit/persistence/criteria/BlazeSelectRecursiveCTECriteria.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 - 2019 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.criteria; + +/** + * + * @param + * @author Jan-Willem Gmelig Meyling + * @since 1.4.0 + */ +public interface BlazeSelectRecursiveCTECriteria extends BlazeSelectBaseCTECriteria { + + BlazeSelectCTECriteria union(); + + BlazeSelectCTECriteria unionAll(); + +} diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/AbstractBlazeSelectBaseCTECriteria.java similarity index 91% rename from jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java rename to jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/AbstractBlazeSelectBaseCTECriteria.java index cd9be44fea..c618f0e378 100644 --- a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCTECriteriaImpl.java +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/AbstractBlazeSelectBaseCTECriteria.java @@ -27,13 +27,16 @@ import com.blazebit.persistence.JoinType; import com.blazebit.persistence.MultipleSubqueryInitiator; import com.blazebit.persistence.OrderByBuilder; +import com.blazebit.persistence.SelectBaseCTECriteriaBuilder; import com.blazebit.persistence.SubqueryBuilder; import com.blazebit.persistence.SubqueryInitiator; import com.blazebit.persistence.WhereBuilder; -import com.blazebit.persistence.criteria.BlazeCTECriteria; +import com.blazebit.persistence.criteria.BlazeFullSelectCTECriteria; import com.blazebit.persistence.criteria.BlazeJoin; import com.blazebit.persistence.criteria.BlazeOrder; import com.blazebit.persistence.criteria.BlazeRoot; +import com.blazebit.persistence.criteria.BlazeSelectBaseCTECriteria; +import com.blazebit.persistence.criteria.BlazeSelectRecursiveCTECriteria; import com.blazebit.persistence.criteria.BlazeSubquery; import com.blazebit.persistence.criteria.impl.expression.AbstractExpression; import com.blazebit.persistence.criteria.impl.expression.AbstractSelection; @@ -68,15 +71,14 @@ import java.util.Map; import java.util.Set; -public class BlazeCTECriteriaImpl implements BlazeCTECriteria { +public abstract class AbstractBlazeSelectBaseCTECriteria implements BlazeSelectBaseCTECriteria { - private final Class returnType; + protected final Class returnType; private final BlazeCriteriaBuilderImpl criteriaBuilder; private final List assignments = new ArrayList(); private final Root path; private boolean distinct; - private Selection selection; private final Set> roots = new LinkedHashSet<>(); private Set> correlationRoots; private Predicate restriction; @@ -85,7 +87,7 @@ public class BlazeCTECriteriaImpl implements BlazeCTECriteria { private List orderList = Collections.emptyList(); private List> subqueries; - public BlazeCTECriteriaImpl(BlazeCriteriaBuilderImpl criteriaBuilder, Class returnType) { + public AbstractBlazeSelectBaseCTECriteria(BlazeCriteriaBuilderImpl criteriaBuilder, Class returnType) { this.returnType = returnType; this.criteriaBuilder = criteriaBuilder; EntityType entityType = criteriaBuilder.getEntityMetamodel().entity(returnType); @@ -93,7 +95,12 @@ public BlazeCTECriteriaImpl(BlazeCriteriaBuilderImpl criteriaBuilder, Class r } @Override - public BlazeCTECriteria with(Class clasz) { + public BlazeFullSelectCTECriteria with(Class clasz) { + return null; + } + + @Override + public BlazeSelectRecursiveCTECriteria withRecursive(Class clasz) { return null; } @@ -137,7 +144,7 @@ public BlazeRoot from(EntityType entityType, String alias) { @Override @SuppressWarnings("unchecked") public Selection getSelection() { - return (Selection) selection; + throw new UnsupportedOperationException(); } @Override @@ -146,34 +153,34 @@ public Class getResultType() { } @Override - public BlazeCTECriteria bind(String attributeName, Object value) { + public AbstractBlazeSelectBaseCTECriteria bind(String attributeName, Object value) { final Path attributePath = path.get(attributeName); return internalSet(attributePath, valueExpression(attributePath, value)); } @Override - public BlazeCTECriteria bind(SingularAttribute attribute, X value) { + public AbstractBlazeSelectBaseCTECriteria bind(SingularAttribute attribute, X value) { Path attributePath = path.get(attribute); return internalSet(attributePath, valueExpression(attributePath, value)); } @Override - public BlazeCTECriteria bind(SingularAttribute attribute, Expression value) { + public AbstractBlazeSelectBaseCTECriteria bind(SingularAttribute attribute, Expression value) { Path attributePath = path.get(attribute); return internalSet(attributePath, value); } @Override - public BlazeCTECriteria bind(Path attribute, X value) { + public AbstractBlazeSelectBaseCTECriteria bind(Path attribute, X value) { return internalSet(attribute, valueExpression(attribute, value)); } @Override - public BlazeCTECriteria bind(Path attribute, Expression value) { + public AbstractBlazeSelectBaseCTECriteria bind(Path attribute, Expression value) { return internalSet(attribute, value); } - private BlazeCTECriteria internalSet(Path attribute, Expression value) { + private AbstractBlazeSelectBaseCTECriteria internalSet(Path attribute, Expression value) { if (!(attribute instanceof AbstractPath)) { throw new IllegalArgumentException("Illegal custom attribute path: " + attribute.getClass().getName()); } @@ -208,13 +215,13 @@ public Predicate getRestriction() { } @Override - public BlazeCTECriteriaImpl where(Expression restriction) { + public AbstractBlazeSelectBaseCTECriteria where(Expression restriction) { this.restriction = restriction == null ? null : criteriaBuilder.wrap(restriction); return this; } @Override - public BlazeCTECriteria where(Predicate... restrictions) { + public AbstractBlazeSelectBaseCTECriteria where(Predicate... restrictions) { if (restrictions == null || restrictions.length == 0) { this.restriction = null; } else { @@ -232,7 +239,7 @@ public List> getGroupList() { @Override @SuppressWarnings("unchecked") - public BlazeCTECriteria groupBy(Expression... groupings) { + public AbstractBlazeSelectBaseCTECriteria groupBy(Expression... groupings) { if (groupings == null || groupings.length == 0) { groupList = Collections.EMPTY_LIST; } else { @@ -243,7 +250,7 @@ public BlazeCTECriteria groupBy(Expression... groupings) { } @Override - public BlazeCTECriteria groupBy(List> groupings) { + public AbstractBlazeSelectBaseCTECriteria groupBy(List> groupings) { groupList = groupings; return this; } @@ -256,7 +263,7 @@ public Predicate getGroupRestriction() { } @Override - public BlazeCTECriteria having(Expression restriction) { + public AbstractBlazeSelectBaseCTECriteria having(Expression restriction) { if (restriction == null) { having = null; } else { @@ -266,7 +273,7 @@ public BlazeCTECriteria having(Expression restriction) { } @Override - public BlazeCTECriteria having(Predicate... restrictions) { + public AbstractBlazeSelectBaseCTECriteria having(Predicate... restrictions) { if (restrictions == null || restrictions.length == 0) { having = null; } else { @@ -281,7 +288,7 @@ public boolean isDistinct() { } @Override - public BlazeCTECriteria distinct(boolean distinct) { + public AbstractBlazeSelectBaseCTECriteria distinct(boolean distinct) { this.distinct = distinct; return this; } @@ -306,7 +313,7 @@ public List getBlazeOrderList() { } @Override - public BlazeCTECriteria orderBy(Order... orders) { + public AbstractBlazeSelectBaseCTECriteria orderBy(Order... orders) { if (orders == null || orders.length == 0) { orderList = Collections.EMPTY_LIST; } else { @@ -317,7 +324,7 @@ public BlazeCTECriteria orderBy(Order... orders) { } @Override - public BlazeCTECriteria orderBy(List orderList) { + public AbstractBlazeSelectBaseCTECriteria orderBy(List orderList) { this.orderList = (List) (List) orderList; return null; } @@ -327,7 +334,10 @@ public Set> getParameters() { // NOTE: we have to always visit them because it's not possible to cache that easily ParameterVisitor visitor = new ParameterVisitor(); - visitor.visit(selection); + for (Assignment assignment : assignments) { + assignment.valueExpression.visitParameters(visitor); + } + visitor.visit(restriction); if (subqueries != null) { for (Subquery subquery : subqueries) { @@ -386,10 +396,13 @@ public Assignment(SingularAttributePath attributePath, AbstractExpression } public CriteriaBuilder render(CriteriaBuilder cbs) { - FullSelectCTECriteriaBuilder> fullSelectCTECriteriaBuilder = cbs.with(returnType); RenderContextImpl context = new RenderContextImpl(); + render(fullSelectCTECriteriaBuilder, context); + return fullSelectCTECriteriaBuilder.end(); + } + protected void render(SelectBaseCTECriteriaBuilder fullSelectCTECriteriaBuilder, RenderContextImpl context) { renderFrom(fullSelectCTECriteriaBuilder, context); renderWhere(fullSelectCTECriteriaBuilder, context); renderGroupBy(fullSelectCTECriteriaBuilder, context); @@ -414,7 +427,7 @@ public CriteriaBuilder render(CriteriaBuilder cbs) { if (aliasToSubqueries.isEmpty()) { fullSelectCTECriteriaBuilder.bind(attribute).select(valueExpression); } else { - MultipleSubqueryInitiator>> initiator = fullSelectCTECriteriaBuilder.bind(attribute).selectSubqueries(valueExpression); + MultipleSubqueryInitiator initiator = fullSelectCTECriteriaBuilder.bind(attribute).selectSubqueries(valueExpression); for (Map.Entry> subqueryEntry : aliasToSubqueries.entrySet()) { context.pushSubqueryInitiator(initiator.with(subqueryEntry.getKey())); @@ -434,8 +447,6 @@ public CriteriaBuilder render(CriteriaBuilder cbs) { for (Map.Entry> entry : context.getExplicitParameterNameMapping().entrySet()) { fullSelectCTECriteriaBuilder.setParameterType(entry.getKey(), entry.getValue().getParameterType()); } - - return fullSelectCTECriteriaBuilder.end(); } @@ -688,7 +699,6 @@ protected void renderWhere(WhereBuilder wb, RenderContextImpl context) { } } - private void renderTreatTypeRestrictions(RenderContextImpl context, List> treatedSelections) { final StringBuilder buffer = context.getBuffer(); boolean first = buffer.length() == 0; diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCriteriaQueryImpl.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCriteriaQueryImpl.java index 1ba55b9285..ec5bafd7ba 100644 --- a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCriteriaQueryImpl.java +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeCriteriaQueryImpl.java @@ -17,11 +17,12 @@ package com.blazebit.persistence.criteria.impl; import com.blazebit.persistence.CriteriaBuilder; -import com.blazebit.persistence.criteria.BlazeCTECriteria; import com.blazebit.persistence.criteria.BlazeCriteriaBuilder; import com.blazebit.persistence.criteria.BlazeCriteriaQuery; +import com.blazebit.persistence.criteria.BlazeFullSelectCTECriteria; import com.blazebit.persistence.criteria.BlazeOrder; import com.blazebit.persistence.criteria.BlazeRoot; +import com.blazebit.persistence.criteria.BlazeSelectRecursiveCTECriteria; import com.blazebit.persistence.criteria.BlazeSubquery; import javax.persistence.EntityManager; @@ -282,8 +283,13 @@ public CriteriaBuilder createCriteriaBuilder(EntityManager entityManager) { } @Override - public BlazeCTECriteria with(Class clasz) { + public BlazeFullSelectCTECriteria with(Class clasz) { return query.with(clasz); } + @Override + public BlazeSelectRecursiveCTECriteria withRecursive(Class clasz) { + return query.withRecursive(clasz); + } + } diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeFullSelectCTECriteriaImpl.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeFullSelectCTECriteriaImpl.java new file mode 100644 index 0000000000..7924cb11e3 --- /dev/null +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeFullSelectCTECriteriaImpl.java @@ -0,0 +1,27 @@ +/* + * Copyright 2014 - 2019 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.criteria.impl; + +import com.blazebit.persistence.criteria.BlazeFullSelectCTECriteria; + +public class BlazeFullSelectCTECriteriaImpl extends AbstractBlazeSelectBaseCTECriteria implements BlazeFullSelectCTECriteria { + + public BlazeFullSelectCTECriteriaImpl(BlazeCriteriaBuilderImpl criteriaBuilder, Class returnType) { + super(criteriaBuilder, returnType); + } + +} diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeSelectCTECriteriaImpl.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeSelectCTECriteriaImpl.java new file mode 100644 index 0000000000..018a354471 --- /dev/null +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeSelectCTECriteriaImpl.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014 - 2019 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.criteria.impl; + +import com.blazebit.persistence.criteria.BlazeSelectCTECriteria; + +public class BlazeSelectCTECriteriaImpl + extends AbstractBlazeSelectBaseCTECriteria // TODO: Introduce abstract class + implements BlazeSelectCTECriteria { + + public BlazeSelectCTECriteriaImpl(BlazeCriteriaBuilderImpl criteriaBuilder, Class returnType) { + super(criteriaBuilder, returnType); + } +} diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeSelectRecursiveCTECriteriaImpl.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeSelectRecursiveCTECriteriaImpl.java new file mode 100644 index 0000000000..45bfad71e8 --- /dev/null +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/BlazeSelectRecursiveCTECriteriaImpl.java @@ -0,0 +1,60 @@ +/* + * Copyright 2014 - 2019 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.criteria.impl; + +import com.blazebit.persistence.CriteriaBuilder; +import com.blazebit.persistence.SelectCTECriteriaBuilder; +import com.blazebit.persistence.SelectRecursiveCTECriteriaBuilder; +import com.blazebit.persistence.criteria.BlazeSelectCTECriteria; +import com.blazebit.persistence.criteria.BlazeSelectRecursiveCTECriteria; + +public class BlazeSelectRecursiveCTECriteriaImpl + extends AbstractBlazeSelectBaseCTECriteria // TODO: Introduce abstract class + implements BlazeSelectRecursiveCTECriteria { + + private final BlazeSelectCTECriteriaImpl recursivePart; + private boolean unionAll; + + public BlazeSelectRecursiveCTECriteriaImpl(BlazeCriteriaBuilderImpl criteriaBuilder, Class returnType) { + super(criteriaBuilder, returnType); + recursivePart = new BlazeSelectCTECriteriaImpl<>(criteriaBuilder, returnType); + } + + @Override + public BlazeSelectCTECriteria union() { + return recursivePart; + } + + @Override + public BlazeSelectCTECriteria unionAll() { + unionAll = true; + return recursivePart; + } + + @Override + public CriteriaBuilder render(CriteriaBuilder cbs) { + SelectRecursiveCTECriteriaBuilder> criteriaBuilder = cbs.withRecursive(returnType); + RenderContextImpl context = new RenderContextImpl(); + render(criteriaBuilder, context); + + SelectCTECriteriaBuilder> criteriaBuilderSelectCTECriteriaBuilder = + unionAll ? criteriaBuilder.unionAll() : criteriaBuilder.union(); + + recursivePart.render(criteriaBuilderSelectCTECriteriaBuilder, context); + return criteriaBuilderSelectCTECriteriaBuilder.end(); + } +} diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/InternalQuery.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/InternalQuery.java index 961605aa74..ed0d703aa4 100644 --- a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/InternalQuery.java +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/InternalQuery.java @@ -32,10 +32,10 @@ import com.blazebit.persistence.SubqueryInitiator; import com.blazebit.persistence.WhereBuilder; import com.blazebit.persistence.criteria.BlazeAbstractQuery; -import com.blazebit.persistence.criteria.BlazeCTECriteria; import com.blazebit.persistence.criteria.BlazeJoin; import com.blazebit.persistence.criteria.BlazeOrder; import com.blazebit.persistence.criteria.BlazeRoot; +import com.blazebit.persistence.criteria.BlazeSelectRecursiveCTECriteria; import com.blazebit.persistence.criteria.BlazeSubquery; import com.blazebit.persistence.criteria.impl.RenderContext.ClauseType; import com.blazebit.persistence.criteria.impl.expression.AbstractSelection; @@ -280,7 +280,7 @@ public CriteriaBuilder render(CriteriaBuilder cb) { RenderContextImpl context = new RenderContextImpl(); if (ctes != null && ctes.size() > 0) { - for (BlazeCTECriteriaImpl cte : ctes) { + for (AbstractBlazeSelectBaseCTECriteria cte : ctes) { cte.render(cb); } } @@ -813,19 +813,24 @@ private void renderOrderBy(OrderByBuilder ob, RenderContextImpl context) { } } - private List> ctes; + private List> ctes; - private List> getCtesInternal() { + private List> getCtesInternal() { if (ctes == null) { ctes = new ArrayList<>(); } return ctes; } - public BlazeCTECriteria with(Class clasz) { - BlazeCTECriteriaImpl cteCriteria = new BlazeCTECriteriaImpl<>(criteriaBuilder, clasz); + public BlazeFullSelectCTECriteriaImpl with(Class clasz) { + BlazeFullSelectCTECriteriaImpl cteCriteria = new BlazeFullSelectCTECriteriaImpl<>(criteriaBuilder, clasz); getCtesInternal().add(cteCriteria); return cteCriteria; } + public BlazeSelectRecursiveCTECriteriaImpl withRecursive(Class clasz) { + BlazeSelectRecursiveCTECriteriaImpl cteCriteria = new BlazeSelectRecursiveCTECriteriaImpl<>(criteriaBuilder, clasz); + getCtesInternal().add(cteCriteria); + return cteCriteria; + } } diff --git a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/expression/SubqueryExpression.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/expression/SubqueryExpression.java index 34379f6871..3c9bdad1fe 100644 --- a/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/expression/SubqueryExpression.java +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/expression/SubqueryExpression.java @@ -17,14 +17,15 @@ package com.blazebit.persistence.criteria.impl.expression; import com.blazebit.persistence.criteria.BlazeAbstractQuery; -import com.blazebit.persistence.criteria.BlazeCTECriteria; import com.blazebit.persistence.criteria.BlazeCollectionJoin; import com.blazebit.persistence.criteria.BlazeCommonAbstractCriteria; +import com.blazebit.persistence.criteria.BlazeFullSelectCTECriteria; import com.blazebit.persistence.criteria.BlazeJoin; import com.blazebit.persistence.criteria.BlazeListJoin; import com.blazebit.persistence.criteria.BlazeMapJoin; import com.blazebit.persistence.criteria.BlazeOrder; import com.blazebit.persistence.criteria.BlazeRoot; +import com.blazebit.persistence.criteria.BlazeSelectRecursiveCTECriteria; import com.blazebit.persistence.criteria.BlazeSetJoin; import com.blazebit.persistence.criteria.BlazeSubquery; import com.blazebit.persistence.criteria.impl.BlazeCriteriaBuilderImpl; @@ -74,7 +75,12 @@ public SubqueryExpression(BlazeCriteriaBuilderImpl criteriaBuilder, Class jav } @Override - public BlazeCTECriteria with(Class clasz) { + public BlazeFullSelectCTECriteria with(Class clasz) { + return null; + } + + @Override + public BlazeSelectRecursiveCTECriteria withRecursive(Class clasz) { return null; } diff --git a/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java b/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java index b6ac1a032f..e30369d441 100644 --- a/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java +++ b/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java @@ -22,6 +22,11 @@ import com.blazebit.persistence.CriteriaBuilderFactory; import com.blazebit.persistence.spi.CriteriaBuilderConfiguration; import com.blazebit.persistence.testsuite.AbstractCoreTest; +import com.blazebit.persistence.testsuite.base.jpa.category.NoDatanucleus; +import com.blazebit.persistence.testsuite.base.jpa.category.NoEclipselink; +import com.blazebit.persistence.testsuite.base.jpa.category.NoMySQLOld; +import com.blazebit.persistence.testsuite.base.jpa.category.NoOpenJPA; +import com.blazebit.persistence.testsuite.base.jpa.category.NoOracle; import com.blazebit.persistence.testsuite.entity.RecursiveEntity; import com.blazebit.persistence.testsuite.entity.RecursiveEntity_; import com.blazebit.persistence.testsuite.entity.TestAdvancedCTE1; @@ -29,9 +34,13 @@ import com.blazebit.persistence.testsuite.entity.TestAdvancedCTE2; import com.blazebit.persistence.testsuite.entity.TestCTE; import com.blazebit.persistence.testsuite.entity.TestCTEEmbeddable_; +import com.blazebit.persistence.testsuite.entity.TestCTE_; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.junit.experimental.categories.Category; + +import static org.junit.Assert.assertEquals; /** * @@ -61,11 +70,12 @@ protected Class[] getEntityClasses() { @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class, NoMySQLOld.class }) public void testBindEmbeddable() { BlazeCriteriaQuery cq = BlazeCriteria.get(cbf, TestAdvancedCTE1.class); BlazeCriteriaBuilder cb = cq.getCriteriaBuilder(); - BlazeCTECriteria documentCte = cq.with(TestAdvancedCTE1.class); + BlazeFullSelectCTECriteria documentCte = cq.with(TestAdvancedCTE1.class); BlazeRoot recursiveEntity = documentCte.from(RecursiveEntity.class); documentCte.bind(TestAdvancedCTE1_.id, recursiveEntity.get(RecursiveEntity_.id)); documentCte.bind(documentCte.get(TestAdvancedCTE1_.embeddable).get(TestCTEEmbeddable_.name), recursiveEntity.get(RecursiveEntity_.name)); @@ -81,4 +91,52 @@ public void testBindEmbeddable() { Assert.assertNotNull(queryString); } + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class, NoMySQLOld.class, NoOracle.class }) + public void testRecursiveCTE() { + BlazeCriteriaQuery cq = BlazeCriteria.get(cbf, TestCTE.class); + BlazeCriteriaBuilder cb = cq.getCriteriaBuilder(); + + BlazeSelectRecursiveCTECriteria testCTE = cq.withRecursive(TestCTE.class); + { + BlazeRoot recursiveEntity = testCTE.from(RecursiveEntity.class, "e"); + testCTE.bind(TestCTE_.id, recursiveEntity.get(RecursiveEntity_.id)); + testCTE.bind(TestCTE_.name, recursiveEntity.get(RecursiveEntity_.name)); + testCTE.bind(TestCTE_.level, 0); + testCTE.where(recursiveEntity.get(RecursiveEntity_.parent).isNull()); + } + + { + BlazeSelectCTECriteria recursivePart = testCTE.unionAll(); + BlazeRoot testCteRoot = recursivePart.from(TestCTE.class, "t"); + BlazeJoin recursiveEntity = testCteRoot.join(RecursiveEntity.class, "e"); + recursiveEntity.on(cb.equal(testCteRoot.get(TestCTE_.id), recursiveEntity.get(RecursiveEntity_.parent).get(RecursiveEntity_.id))); + recursivePart.bind(TestCTE_.id, recursiveEntity.get(RecursiveEntity_.id)); + recursivePart.bind(TestCTE_.name, recursiveEntity.get(RecursiveEntity_.name)); + recursivePart.bind(TestCTE_.level, cb.sum(testCteRoot.get(TestCTE_.level), 1)); + } + + BlazeRoot t = cq.from(TestCTE.class, "t"); + cq.where(cb.lessThan(t.get(TestCTE_.level), 2)); + + CriteriaBuilder criteriaBuilder = cq.createCriteriaBuilder(em); + String expected = "" + + "WITH RECURSIVE " + TestCTE.class.getSimpleName() + "(id, name, level) AS(\n" + + "SELECT e.id, e.name, 0 FROM RecursiveEntity e WHERE e.parent IS NULL" + + "\nUNION ALL\n" + + "SELECT e.id, e.name, t.level + 1 FROM " + TestCTE.class.getSimpleName() + " t" + innerJoinRecursive("RecursiveEntity e", "t.id = e.parent.id") + + "\n)\n" + + "SELECT t FROM " + TestCTE.class.getSimpleName() + " t WHERE t.level < 2"; + + assertEquals(expected, criteriaBuilder.getQueryString()); + } + + private String innerJoinRecursive(String entityFragment, String onPredicate) { + if (jpaProvider.supportsEntityJoin() && dbmsDialect.supportsJoinsInRecursiveCte()) { + return " JOIN " + entityFragment + onClause(onPredicate); + } else { + return ", " + entityFragment + " WHERE " + onPredicate; + } + } + }