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..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,6 +37,10 @@ public interface BlazeAbstractQuery extends AbstractQuery, BlazeCommonAbst // TODO: integrate support for default join nodes? // TODO: maybe add explicit support for limit? + 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/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/AbstractBlazeSelectBaseCTECriteria.java b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/AbstractBlazeSelectBaseCTECriteria.java new file mode 100644 index 0000000000..c618f0e378 --- /dev/null +++ b/jpa-criteria/impl/src/main/java/com/blazebit/persistence/criteria/impl/AbstractBlazeSelectBaseCTECriteria.java @@ -0,0 +1,810 @@ +/* + * 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.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.SelectBaseCTECriteriaBuilder; +import com.blazebit.persistence.SubqueryBuilder; +import com.blazebit.persistence.SubqueryInitiator; +import com.blazebit.persistence.WhereBuilder; +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; +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; +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.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 abstract class AbstractBlazeSelectBaseCTECriteria implements BlazeSelectBaseCTECriteria { + + protected final Class returnType; + private final BlazeCriteriaBuilderImpl criteriaBuilder; + private final List assignments = new ArrayList(); + private final Root path; + + private boolean distinct; + 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 AbstractBlazeSelectBaseCTECriteria(BlazeCriteriaBuilderImpl criteriaBuilder, Class returnType) { + this.returnType = returnType; + this.criteriaBuilder = criteriaBuilder; + EntityType entityType = criteriaBuilder.getEntityMetamodel().entity(returnType); + this.path = new RootImpl(criteriaBuilder, entityType, null, false); + } + + @Override + public BlazeFullSelectCTECriteria with(Class clasz) { + return null; + } + + @Override + public BlazeSelectRecursiveCTECriteria withRecursive(Class clasz) { + return null; + } + + @Override + @SuppressWarnings("unchecked") + public Set> getRoots() { + return (Set>) (Set) roots; + } + + @Override + @SuppressWarnings("unchecked") + public Set> getBlazeRoots() { + return (Set>) (Set) roots; } + + @Override + public BlazeRoot from(Class entityClass) { + return from(entityClass, null); + } + + @Override + public BlazeRoot from(EntityType entityType) { + return from(entityType, null); + } + + @Override + public BlazeRoot from(Class entityClass, String 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) { + RootImpl root = new RootImpl(criteriaBuilder, entityType, alias, true); + roots.add(root); + return root; + } + + @Override + @SuppressWarnings("unchecked") + public Selection getSelection() { + throw new UnsupportedOperationException(); + } + + @Override + public Class getResultType() { + return returnType; + } + + @Override + public AbstractBlazeSelectBaseCTECriteria bind(String attributeName, Object value) { + final Path attributePath = path.get(attributeName); + return internalSet(attributePath, valueExpression(attributePath, value)); + } + + @Override + public AbstractBlazeSelectBaseCTECriteria bind(SingularAttribute attribute, X value) { + Path attributePath = path.get(attribute); + return internalSet(attributePath, valueExpression(attributePath, value)); + } + + @Override + public AbstractBlazeSelectBaseCTECriteria bind(SingularAttribute attribute, Expression value) { + Path attributePath = path.get(attribute); + return internalSet(attributePath, value); + } + + @Override + public AbstractBlazeSelectBaseCTECriteria bind(Path attribute, X value) { + return internalSet(attribute, valueExpression(attribute, value)); + } + + @Override + public AbstractBlazeSelectBaseCTECriteria bind(Path attribute, Expression value) { + return internalSet(attribute, value); + } + + private AbstractBlazeSelectBaseCTECriteria 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 restriction; + } + + @Override + public AbstractBlazeSelectBaseCTECriteria where(Expression restriction) { + this.restriction = restriction == null ? null : criteriaBuilder.wrap(restriction); + return this; + } + + @Override + public AbstractBlazeSelectBaseCTECriteria where(Predicate... restrictions) { + if (restrictions == null || restrictions.length == 0) { + this.restriction = null; + } else { + this.restriction = criteriaBuilder.and(restrictions); + } + return this; + } + + /* Group by */ + + @Override + public List> getGroupList() { + return groupList; + } + + @Override + @SuppressWarnings("unchecked") + public AbstractBlazeSelectBaseCTECriteria groupBy(Expression... groupings) { + if (groupings == null || groupings.length == 0) { + groupList = Collections.EMPTY_LIST; + } else { + groupList = Arrays.asList(groupings); + } + + return this; + } + + @Override + public AbstractBlazeSelectBaseCTECriteria groupBy(List> groupings) { + groupList = groupings; + return this; + } + + /* Having */ + + @Override + public Predicate getGroupRestriction() { + return having; + } + + @Override + public AbstractBlazeSelectBaseCTECriteria having(Expression restriction) { + if (restriction == null) { + having = null; + } else { + having = criteriaBuilder.wrap(restriction); + } + return this; + } + + @Override + public AbstractBlazeSelectBaseCTECriteria having(Predicate... restrictions) { + if (restrictions == null || restrictions.length == 0) { + having = null; + } else { + having = criteriaBuilder.and(restrictions); + } + return this; + } + + @Override + public boolean isDistinct() { + return distinct; + } + + @Override + public AbstractBlazeSelectBaseCTECriteria distinct(boolean distinct) { + this.distinct = distinct; + return this; + } + + public List> internalGetSubqueries() { + if (subqueries == null) { + subqueries = new ArrayList>(); + } + return subqueries; + } + + @Override + public BlazeSubquery subquery(Class type) { + SubqueryExpression subquery = new SubqueryExpression(criteriaBuilder, type, this); + internalGetSubqueries().add(subquery); + return subquery; + } + + @Override + public List getBlazeOrderList() { + return orderList; + } + + @Override + public AbstractBlazeSelectBaseCTECriteria orderBy(Order... orders) { + if (orders == null || orders.length == 0) { + orderList = Collections.EMPTY_LIST; + } else { + orderList = (List) (List) Arrays.asList(orders); + } + + return this; + } + + @Override + public AbstractBlazeSelectBaseCTECriteria 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(); + + for (Assignment assignment : assignments) { + assignment.valueExpression.visitParameters(visitor); + } + + 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; + } + } + + 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); + 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) { + fullSelectCTECriteriaBuilder.bind(attribute).select(valueExpression); + } else { + 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(); + } + } + } + + for (ImplicitParameterBinding b : context.getImplicitParameterBindings()) { + b.bind(fullSelectCTECriteriaBuilder); + } + + for (Map.Entry> entry : context.getExplicitParameterNameMapping().entrySet()) { + fullSelectCTECriteriaBuilder.setParameterType(entry.getKey(), entry.getValue().getParameterType()); + } + } + + + 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 93921ce0ef..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 @@ -19,8 +19,10 @@ import com.blazebit.persistence.CriteriaBuilder; 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; @@ -280,4 +282,14 @@ public CriteriaBuilder createCriteriaBuilder(EntityManager entityManager) { return query.render(cb); } + @Override + 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 f43c22e8e7..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 @@ -35,6 +35,7 @@ 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; @@ -277,6 +278,13 @@ public CriteriaBuilder render(CriteriaBuilder cb) { } RenderContextImpl context = new RenderContextImpl(); + + if (ctes != null && ctes.size() > 0) { + for (AbstractBlazeSelectBaseCTECriteria cte : ctes) { + cte.render(cb); + } + } + renderFrom(cb, context); List> treatedSelections = renderSelect(cb, context); @@ -805,4 +813,24 @@ private void renderOrderBy(OrderByBuilder ob, RenderContextImpl context) { } } + private List> ctes; + + private List> getCtesInternal() { + if (ctes == null) { + ctes = new ArrayList<>(); + } + return ctes; + } + + 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 bd3ad59e20..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 @@ -19,11 +19,13 @@ import com.blazebit.persistence.criteria.BlazeAbstractQuery; 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; @@ -72,6 +74,16 @@ public SubqueryExpression(BlazeCriteriaBuilderImpl criteriaBuilder, Class jav this.query = new InternalQuery(this, criteriaBuilder); } + @Override + public BlazeFullSelectCTECriteria with(Class clasz) { + return null; + } + + @Override + public BlazeSelectRecursiveCTECriteria withRecursive(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 new file mode 100644 index 0000000000..e30369d441 --- /dev/null +++ b/jpa-criteria/testsuite/src/test/java/com/blazebit/persistence/criteria/CteTest.java @@ -0,0 +1,142 @@ +/* + * 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.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; +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 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; + +/** + * + * @author Jan-Willem Gmelig Meyling + * @since 1.4.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); + } + + @Override + protected Class[] getEntityClasses() { + return new Class[] { + RecursiveEntity.class, + TestCTE.class, + TestAdvancedCTE1.class, + TestAdvancedCTE2.class + }; + } + + + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class, NoMySQLOld.class }) + public void testBindEmbeddable() { + BlazeCriteriaQuery cq = BlazeCriteria.get(cbf, TestAdvancedCTE1.class); + BlazeCriteriaBuilder cb = cq.getCriteriaBuilder(); + + 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)); + 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.from(TestAdvancedCTE1.class); + + CriteriaBuilder criteriaBuilder = cq.createCriteriaBuilder(em); + String queryString = criteriaBuilder.getQueryString(); + 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; + } + } + +}