diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractFullQueryBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractFullQueryBuilder.java index 790cc4f04f..dcadaef2d8 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractFullQueryBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractFullQueryBuilder.java @@ -1,269 +1,269 @@ -/* - * Copyright 2014 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.impl; - -import java.lang.reflect.Constructor; - -import javax.persistence.metamodel.EntityType; - -import com.blazebit.persistence.FullQueryBuilder; -import com.blazebit.persistence.JoinType; -import com.blazebit.persistence.KeysetPage; -import com.blazebit.persistence.ObjectBuilder; -import com.blazebit.persistence.PaginatedCriteriaBuilder; -import com.blazebit.persistence.SelectObjectBuilder; - -/** - * - * @param The query result type - * @param The concrete builder type - * @author Christian Beikov - * @author Moritz Becker - * @since 1.0 - */ -public abstract class AbstractFullQueryBuilder, Z, W, FinalSetReturn extends BaseFinalSetOperationBuilderImpl> extends AbstractQueryBuilder implements FullQueryBuilder { - - /** - * This flag indicates whether the current builder has been used to create a - * PaginatedCriteriaBuilder. In this case we must not allow any calls to - * group by and distinct since the corresponding managers are shared with - * the PaginatedCriteriaBuilder and any changes would affect the - * PaginatedCriteriaBuilder as well. - */ - private boolean createdPaginatedBuilder = false; - - /** - * Create flat copy of builder - * - * @param builder - */ - protected AbstractFullQueryBuilder(AbstractFullQueryBuilder, ?, ?, ?> builder) { - super(builder); - } - - public AbstractFullQueryBuilder(MainQuery mainQuery, boolean isMainQuery, Class clazz, String alias, FinalSetReturn finalSetOperationBuilder) { - super(mainQuery, isMainQuery, clazz, alias, finalSetOperationBuilder); - } - - @Override - public PaginatedCriteriaBuilder page(int firstRow, int pageSize) { - clearCache(); - if (selectManager.isDistinct()) { - throw new IllegalStateException("Cannot paginate a DISTINCT query"); - } - if (!groupByManager.isEmpty()) { - throw new IllegalStateException("Cannot paginate a GROUP BY query"); - } - createdPaginatedBuilder = true; - return new PaginatedCriteriaBuilderImpl(this, false, null, firstRow, pageSize); - } - - @Override - public PaginatedCriteriaBuilder page(Object entityId, int pageSize) { - clearCache(); - if (selectManager.isDistinct()) { - throw new IllegalStateException("Cannot paginate a DISTINCT query"); - } - if (!groupByManager.isEmpty()) { - throw new IllegalStateException("Cannot paginate a GROUP BY query"); - } - checkEntityId(entityId); - createdPaginatedBuilder = true; - return new PaginatedCriteriaBuilderImpl(this, false, null, entityId, pageSize); - } - - @Override - public PaginatedCriteriaBuilder page(KeysetPage keysetPage, int firstRow, int pageSize) { - clearCache(); - if (selectManager.isDistinct()) { - throw new IllegalStateException("Cannot paginate a DISTINCT query"); - } - if (!groupByManager.isEmpty()) { - throw new IllegalStateException("Cannot paginate a GROUP BY query"); - } - createdPaginatedBuilder = true; - return new PaginatedCriteriaBuilderImpl(this, true, keysetPage, firstRow, pageSize); - } - - private void checkEntityId(Object entityId) { - if (entityId == null) { - throw new IllegalArgumentException("Invalid null entity id given"); - } - - EntityType entityType = em.getMetamodel().entity(joinManager.getRootNodeOrFail("Paginated queries do not support multiple from clause elements!").getPropertyClass()); - Class idType = entityType.getIdType().getJavaType(); - - if (!idType.isInstance(entityId)) { - throw new IllegalArgumentException("The type of the given entity id '" + entityId.getClass().getName() - + "' is not an instance of the expected id type '" + idType.getName() + "' of the entity class '" + entityType.getJavaType().getName() + "'"); - } - } - - @Override - public SelectObjectBuilder> selectNew(Class clazz) { - clearCache(); - if (clazz == null) { - throw new NullPointerException("clazz"); - } - - verifyBuilderEnded(); - return selectManager.selectNew(this, clazz); - } - - @Override - public SelectObjectBuilder> selectNew(Constructor constructor) { - clearCache(); - if (constructor == null) { - throw new NullPointerException("constructor"); - } - - verifyBuilderEnded(); - return selectManager.selectNew(this, constructor); - } - - @Override - @SuppressWarnings("unchecked") - public FullQueryBuilder selectNew(ObjectBuilder objectBuilder) { - clearCache(); - if (objectBuilder == null) { - throw new NullPointerException("objectBuilder"); - } - - verifyBuilderEnded(); - selectManager.selectNew((X) this, objectBuilder); - return (FullQueryBuilder) this; - } - - private void checkFetchJoinAllowed() { - if (selectManager.getSelectInfos().size() > 0) { - throw new IllegalStateException("Fetch joins are only possible if the root entity is selected"); - } - } - - @Override - @SuppressWarnings("unchecked") - public X fetch(String path) { - clearCache(); - checkFetchJoinAllowed(); - verifyBuilderEnded(); - joinManager.implicitJoin(expressionFactory.createSimpleExpression(path), true, null, false, false, true, true); - return (X) this; - } - - @Override - @SuppressWarnings("unchecked") - public X fetch(String... paths) { - clearCache(); - checkFetchJoinAllowed(); - verifyBuilderEnded(); - - for (String path : paths) { - joinManager.implicitJoin(expressionFactory.createSimpleExpression(path), true, null, false, false, true, true); - } - - return (X) this; - } - - @Override - public X innerJoinFetch(String path, String alias) { - return join(path, alias, JoinType.INNER, true); - } - - @Override - public X innerJoinFetchDefault(String path, String alias) { - return joinDefault(path, alias, JoinType.INNER, true); - } - - @Override - public X leftJoinFetch(String path, String alias) { - return join(path, alias, JoinType.LEFT, true); - } - - @Override - public X leftJoinFetchDefault(String path, String alias) { - return joinDefault(path, alias, JoinType.LEFT, true); - } - - @Override - public X rightJoinFetch(String path, String alias) { - return join(path, alias, JoinType.RIGHT, true); - } - - @Override - public X rightJoinFetchDefault(String path, String alias) { - return joinDefault(path, alias, JoinType.RIGHT, true); - } - - @Override - public X join(String path, String alias, JoinType type, boolean fetch) { - return join(path, alias, type, fetch, false); - } - - @Override - public X joinDefault(String path, String alias, JoinType type, boolean fetch) { - return join(path, alias, type, fetch, true); - } - - @SuppressWarnings("unchecked") - private X join(String path, String alias, JoinType type, boolean fetch, boolean defaultJoin) { - clearCache(); - if (path == null) { - throw new NullPointerException("path"); - } - if (alias == null) { - throw new NullPointerException("alias"); - } - if (type == null) { - throw new NullPointerException("type"); - } - if (alias.isEmpty()) { - throw new IllegalArgumentException("Empty alias"); - } - - if (fetch == true) { - checkFetchJoinAllowed(); - } - - verifyBuilderEnded(); - joinManager.join(path, alias, type, fetch, defaultJoin); - return (X) this; - } - - @Override - public X groupBy(String expression) { - if (createdPaginatedBuilder) { - throw new IllegalStateException("Calling groupBy() on a PaginatedCriteriaBuilder is not allowed."); - } - return super.groupBy(expression); - } - - @Override - public X groupBy(String... paths) { - if (createdPaginatedBuilder) { - throw new IllegalStateException("Calling groupBy() on a PaginatedCriteriaBuilder is not allowed."); - } - return super.groupBy(paths); - } - - @Override - public X distinct() { - if (createdPaginatedBuilder) { - throw new IllegalStateException("Calling distinct() on a PaginatedCriteriaBuilder is not allowed."); - } - return super.distinct(); - } - -} +/* + * Copyright 2014 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.impl; + +import java.lang.reflect.Constructor; + +import javax.persistence.metamodel.EntityType; + +import com.blazebit.persistence.FullQueryBuilder; +import com.blazebit.persistence.JoinType; +import com.blazebit.persistence.KeysetPage; +import com.blazebit.persistence.ObjectBuilder; +import com.blazebit.persistence.PaginatedCriteriaBuilder; +import com.blazebit.persistence.SelectObjectBuilder; + +/** + * + * @param The query result type + * @param The concrete builder type + * @author Christian Beikov + * @author Moritz Becker + * @since 1.0 + */ +public abstract class AbstractFullQueryBuilder, Z, W, FinalSetReturn extends BaseFinalSetOperationBuilderImpl> extends AbstractQueryBuilder implements FullQueryBuilder { + + /** + * This flag indicates whether the current builder has been used to create a + * PaginatedCriteriaBuilder. In this case we must not allow any calls to + * group by and distinct since the corresponding managers are shared with + * the PaginatedCriteriaBuilder and any changes would affect the + * PaginatedCriteriaBuilder as well. + */ + private boolean createdPaginatedBuilder = false; + + /** + * Create flat copy of builder + * + * @param builder + */ + protected AbstractFullQueryBuilder(AbstractFullQueryBuilder, ?, ?, ?> builder) { + super(builder); + } + + public AbstractFullQueryBuilder(MainQuery mainQuery, boolean isMainQuery, Class clazz, String alias, FinalSetReturn finalSetOperationBuilder) { + super(mainQuery, isMainQuery, clazz, alias, finalSetOperationBuilder); + } + + @Override + public PaginatedCriteriaBuilder page(int firstRow, int pageSize) { + clearCache(); + if (selectManager.isDistinct()) { + throw new IllegalStateException("Cannot paginate a DISTINCT query"); + } + if (!groupByManager.isEmpty()) { + throw new IllegalStateException("Cannot paginate a GROUP BY query"); + } + createdPaginatedBuilder = true; + return new PaginatedCriteriaBuilderImpl(this, false, null, firstRow, pageSize); + } + + @Override + public PaginatedCriteriaBuilder page(Object entityId, int pageSize) { + clearCache(); + if (selectManager.isDistinct()) { + throw new IllegalStateException("Cannot paginate a DISTINCT query"); + } + if (!groupByManager.isEmpty()) { + throw new IllegalStateException("Cannot paginate a GROUP BY query"); + } + checkEntityId(entityId); + createdPaginatedBuilder = true; + return new PaginatedCriteriaBuilderImpl(this, false, null, entityId, pageSize); + } + + @Override + public PaginatedCriteriaBuilder page(KeysetPage keysetPage, int firstRow, int pageSize) { + clearCache(); + if (selectManager.isDistinct()) { + throw new IllegalStateException("Cannot paginate a DISTINCT query"); + } + if (!groupByManager.isEmpty()) { + throw new IllegalStateException("Cannot paginate a GROUP BY query"); + } + createdPaginatedBuilder = true; + return new PaginatedCriteriaBuilderImpl(this, true, keysetPage, firstRow, pageSize); + } + + private void checkEntityId(Object entityId) { + if (entityId == null) { + throw new IllegalArgumentException("Invalid null entity id given"); + } + + EntityType entityType = em.getMetamodel().entity(joinManager.getRootNodeOrFail("Paginated queries do not support multiple from clause elements!").getPropertyClass()); + Class idType = entityType.getIdType().getJavaType(); + + if (!idType.isInstance(entityId)) { + throw new IllegalArgumentException("The type of the given entity id '" + entityId.getClass().getName() + + "' is not an instance of the expected id type '" + idType.getName() + "' of the entity class '" + entityType.getJavaType().getName() + "'"); + } + } + + @Override + public SelectObjectBuilder> selectNew(Class clazz) { + clearCache(); + if (clazz == null) { + throw new NullPointerException("clazz"); + } + + verifyBuilderEnded(); + return selectManager.selectNew(this, clazz); + } + + @Override + public SelectObjectBuilder> selectNew(Constructor constructor) { + clearCache(); + if (constructor == null) { + throw new NullPointerException("constructor"); + } + + verifyBuilderEnded(); + return selectManager.selectNew(this, constructor); + } + + @Override + @SuppressWarnings("unchecked") + public FullQueryBuilder selectNew(ObjectBuilder objectBuilder) { + clearCache(); + if (objectBuilder == null) { + throw new NullPointerException("objectBuilder"); + } + + verifyBuilderEnded(); + selectManager.selectNew((X) this, objectBuilder); + return (FullQueryBuilder) this; + } + + private void checkFetchJoinAllowed() { + if (selectManager.getSelectInfos().size() > 0) { + throw new IllegalStateException("Fetch joins are only possible if the root entity is selected"); + } + } + + @Override + @SuppressWarnings("unchecked") + public X fetch(String path) { + clearCache(); + checkFetchJoinAllowed(); + verifyBuilderEnded(); + joinManager.implicitJoin(expressionFactory.createSimpleExpression(path), true, null, null, false, false, true, true); + return (X) this; + } + + @Override + @SuppressWarnings("unchecked") + public X fetch(String... paths) { + clearCache(); + checkFetchJoinAllowed(); + verifyBuilderEnded(); + + for (String path : paths) { + joinManager.implicitJoin(expressionFactory.createSimpleExpression(path), true, null, null, false, false, true, true); + } + + return (X) this; + } + + @Override + public X innerJoinFetch(String path, String alias) { + return join(path, alias, JoinType.INNER, true); + } + + @Override + public X innerJoinFetchDefault(String path, String alias) { + return joinDefault(path, alias, JoinType.INNER, true); + } + + @Override + public X leftJoinFetch(String path, String alias) { + return join(path, alias, JoinType.LEFT, true); + } + + @Override + public X leftJoinFetchDefault(String path, String alias) { + return joinDefault(path, alias, JoinType.LEFT, true); + } + + @Override + public X rightJoinFetch(String path, String alias) { + return join(path, alias, JoinType.RIGHT, true); + } + + @Override + public X rightJoinFetchDefault(String path, String alias) { + return joinDefault(path, alias, JoinType.RIGHT, true); + } + + @Override + public X join(String path, String alias, JoinType type, boolean fetch) { + return join(path, alias, type, fetch, false); + } + + @Override + public X joinDefault(String path, String alias, JoinType type, boolean fetch) { + return join(path, alias, type, fetch, true); + } + + @SuppressWarnings("unchecked") + private X join(String path, String alias, JoinType type, boolean fetch, boolean defaultJoin) { + clearCache(); + if (path == null) { + throw new NullPointerException("path"); + } + if (alias == null) { + throw new NullPointerException("alias"); + } + if (type == null) { + throw new NullPointerException("type"); + } + if (alias.isEmpty()) { + throw new IllegalArgumentException("Empty alias"); + } + + if (fetch == true) { + checkFetchJoinAllowed(); + } + + verifyBuilderEnded(); + joinManager.join(path, alias, type, fetch, defaultJoin); + return (X) this; + } + + @Override + public X groupBy(String expression) { + if (createdPaginatedBuilder) { + throw new IllegalStateException("Calling groupBy() on a PaginatedCriteriaBuilder is not allowed."); + } + return super.groupBy(expression); + } + + @Override + public X groupBy(String... paths) { + if (createdPaginatedBuilder) { + throw new IllegalStateException("Calling groupBy() on a PaginatedCriteriaBuilder is not allowed."); + } + return super.groupBy(paths); + } + + @Override + public X distinct() { + if (createdPaginatedBuilder) { + throw new IllegalStateException("Calling distinct() on a PaginatedCriteriaBuilder is not allowed."); + } + return super.distinct(); + } + +} diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/CriteriaBuilderFactoryImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/CriteriaBuilderFactoryImpl.java index d5c7118e52..d7d5414b5d 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/CriteriaBuilderFactoryImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/CriteriaBuilderFactoryImpl.java @@ -50,6 +50,7 @@ */ public class CriteriaBuilderFactoryImpl implements CriteriaBuilderFactory { + private final EntityMetamodel metamodel; private final List queryTransformers; private final ExtendedQuerySupport extendedQuerySupport; private final Map dbmsDialects; @@ -63,12 +64,15 @@ public class CriteriaBuilderFactoryImpl implements CriteriaBuilderFactory { private final Set configuredRegisteredFunctions; public CriteriaBuilderFactoryImpl(CriteriaBuilderConfigurationImpl config, EntityManagerFactory entityManagerFactory) { + final boolean compatibleMode = Boolean.valueOf(config.getProperty(ConfigurationProperties.COMPATIBLE_MODE)); + + this.metamodel = new EntityMetamodel(entityManagerFactory.getMetamodel()); this.queryTransformers = new ArrayList(config.getQueryTransformers()); this.extendedQuerySupport = config.getExtendedQuerySupport(); this.dbmsDialects = new HashMap(config.getDbmsDialects()); this.aggregateFunctions = resolveAggregateFunctions(config.getFunctions()); - this.expressionFactory = new SimpleCachingExpressionFactory(new ExpressionFactoryImpl(aggregateFunctions)); - this.subqueryExpressionFactory = new SubqueryExpressionFactory(aggregateFunctions, expressionFactory); + this.expressionFactory = new SimpleCachingExpressionFactory(new ExpressionFactoryImpl(aggregateFunctions, !compatibleMode)); + this.subqueryExpressionFactory = new SubqueryExpressionFactory(aggregateFunctions, !compatibleMode, expressionFactory); this.properties = copyProperties(config.getProperties()); EntityManagerFactory emf = entityManagerFactory; @@ -102,6 +106,10 @@ private static Set resolveAggregateFunctions(Map getQueryTransformers() { return queryTransformers; } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/EntityMetamodel.java b/core/impl/src/main/java/com/blazebit/persistence/impl/EntityMetamodel.java new file mode 100644 index 0000000000..e42b8de97c --- /dev/null +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/EntityMetamodel.java @@ -0,0 +1,85 @@ +package com.blazebit.persistence.impl; + +import javax.persistence.metamodel.EmbeddableType; +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.ManagedType; +import javax.persistence.metamodel.Metamodel; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * This is a wrapper around the JPA {@link Metamodel} allows additionally efficient access by other attributes than a Class. + * + * @author Christian Beikov + * @since 1.2 + */ +public class EntityMetamodel implements Metamodel { + + private final Metamodel delegate; + private final Map> entityNameMap; + private final Map, ManagedType> classMap; + + public EntityMetamodel(Metamodel delegate) { + this.delegate = delegate; + Set> managedTypes = delegate.getManagedTypes(); + Map> nameToType = new HashMap>(managedTypes.size()); + Map, ManagedType> classToType = new HashMap, ManagedType>(managedTypes.size()); + + for (ManagedType t : managedTypes) { + if (t instanceof EntityType) { + EntityType e = (EntityType) t; + nameToType.put(e.getName(), e); + } + classToType.put(t.getJavaType(), t); + } + + this.entityNameMap = Collections.unmodifiableMap(nameToType); + this.classMap = Collections.unmodifiableMap(classToType); + } + + @Override + public EntityType entity(Class cls) { + return delegate.entity(cls); + } + + @Override + public ManagedType managedType(Class cls) { + return delegate.managedType(cls); + } + + public ManagedType managedType(String name) { + ManagedType t = entityNameMap.get(name); + if (t == null) { + throw new IllegalStateException("Managed type with name '" + name + "' does not exist!"); + } + + return t; + } + + @SuppressWarnings({ "unchecked" }) + public ManagedType getManagedType(Class cls) { + return (ManagedType) classMap.get(cls); + } + + @Override + public EmbeddableType embeddable(Class cls) { + return delegate.embeddable(cls); + } + + @Override + public Set> getManagedTypes() { + return delegate.getManagedTypes(); + } + + @Override + public Set> getEntities() { + return delegate.getEntities(); + } + + @Override + public Set> getEmbeddables() { + return delegate.getEmbeddables(); + } +} diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/EntitySelectResolveVisitor.java b/core/impl/src/main/java/com/blazebit/persistence/impl/EntitySelectResolveVisitor.java index 197f1c7cd1..db224c2a4c 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/EntitySelectResolveVisitor.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/EntitySelectResolveVisitor.java @@ -1,107 +1,103 @@ -package com.blazebit.persistence.impl; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; - -import javax.persistence.FetchType; -import javax.persistence.metamodel.Attribute; -import javax.persistence.metamodel.EntityType; -import javax.persistence.metamodel.Metamodel; - -import com.blazebit.persistence.impl.expression.AggregateExpression; -import com.blazebit.persistence.impl.expression.FunctionExpression; -import com.blazebit.persistence.impl.expression.PathElementExpression; -import com.blazebit.persistence.impl.expression.PathExpression; -import com.blazebit.persistence.impl.expression.SimplePathReference; -import com.blazebit.persistence.impl.expression.VisitorAdapter; - -/** - * This visitor resolves entity references to their attributes. This is needed for entity references - * in the select clause when used in combination with aggregate functions. We have to decompose the - * entity and add the components to the group by because all component will end up in the select clause. - * - * @author Christian Beikov - * @since 1.0.5 - */ -public class EntitySelectResolveVisitor extends VisitorAdapter { - - private final Metamodel m; - private final Set pathExpressions; - - public EntitySelectResolveVisitor(Metamodel m) { - this(m, new LinkedHashSet()); - } - - public EntitySelectResolveVisitor(Metamodel m, Set pathExpressions) { - this.m = m; - this.pathExpressions = pathExpressions; - } - - public Set getPathExpressions() { - return pathExpressions; - } - - @Override - public void visit(FunctionExpression expression) { - if (!(expression instanceof AggregateExpression)) { - super.visit(expression); - } - } - - @Override - public void visit(PathExpression expression) { - if (expression.getField() == null) { - /** - * We need to resolve entity selects because hibernate will - * select every entity attribute. Since we need every select in - * the group by (because of DB2) we need to resolve such entity - * selects here - */ - JoinNode baseNode = ((JoinNode) expression.getBaseNode()); - EntityType entityType; - - try { - entityType = m.entity(baseNode.getPropertyClass()); - } catch (IllegalArgumentException e) { - // ignore if the expression is not an entity - return; - } - - // we need to ensure a deterministic order for testing - SortedSet> sortedAttributes = new TreeSet>(new Comparator>() { - - @Override - public int compare(Attribute o1, Attribute o2) { - return o1.getName().compareTo(o2.getName()); - } - - }); - sortedAttributes.addAll(entityType.getAttributes()); - for (Attribute attr : sortedAttributes) { - boolean resolve = false; - if (ExpressionUtils.isAssociation(attr) && !attr.isCollection()) { - resolve = true; - } else if (ExpressionUtils.getFetchType(attr) == FetchType.EAGER) { - if (attr.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION) { - throw new UnsupportedOperationException("Eager element collections are not supported"); - } - resolve = true; - } - - if (resolve) { - PathExpression attrPath = new PathExpression(new ArrayList(expression.getExpressions())); - attrPath.setPathReference(new SimplePathReference(baseNode, attr.getName())); - pathExpressions.add(attrPath); - } - } - } - } - - public void resolve(JoinNode baseNode) { - - } +package com.blazebit.persistence.impl; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.persistence.FetchType; +import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.Metamodel; + +import com.blazebit.persistence.impl.expression.*; + +/** + * This visitor resolves entity references to their attributes. This is needed for entity references + * in the select clause when used in combination with aggregate functions. We have to decompose the + * entity and add the components to the group by because all components will end up in the select clause. + * + * @author Christian Beikov + * @since 1.0.5 + */ +public class EntitySelectResolveVisitor extends VisitorAdapter { + + private final Metamodel m; + private final Set pathExpressions; + + public EntitySelectResolveVisitor(Metamodel m) { + this(m, new LinkedHashSet()); + } + + public EntitySelectResolveVisitor(Metamodel m, Set pathExpressions) { + this.m = m; + this.pathExpressions = pathExpressions; + } + + public Set getPathExpressions() { + return pathExpressions; + } + + @Override + public void visit(FunctionExpression expression) { + if (!(expression instanceof AggregateExpression)) { + super.visit(expression); + } + } + + @Override + public void visit(PathExpression expression) { + if (expression.getField() == null) { + /** + * We need to resolve entity selects because hibernate will + * select every entity attribute. Since we need every select in + * the group by (because of DB2) we need to resolve such entity + * selects here + */ + JoinNode baseNode = ((JoinNode) expression.getBaseNode()); + EntityType entityType; + + try { + entityType = m.entity(baseNode.getPropertyClass()); + } catch (IllegalArgumentException e) { + // ignore if the expression is not an entity + return; + } + + // we need to ensure a deterministic order for testing + SortedSet> sortedAttributes = new TreeSet>(new Comparator>() { + + @Override + public int compare(Attribute o1, Attribute o2) { + return o1.getName().compareTo(o2.getName()); + } + + }); + // TODO: a polymorphic query will fail because we don't collect subtype properties + sortedAttributes.addAll(entityType.getAttributes()); + for (Attribute attr : sortedAttributes) { + boolean resolve = false; + if (ExpressionUtils.isAssociation(attr) && !attr.isCollection()) { + resolve = true; + } else if (ExpressionUtils.getFetchType(attr) == FetchType.EAGER) { + if (attr.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION) { + throw new UnsupportedOperationException("Eager element collections are not supported"); + } + resolve = true; + } + + if (resolve) { + PathExpression attrPath = new PathExpression(new ArrayList(expression.getExpressions())); + attrPath.setPathReference(new SimplePathReference(baseNode, attr.getName(), null)); + pathExpressions.add(attrPath); + } + } + } + } + + public void resolve(JoinNode baseNode) { + + } } \ No newline at end of file diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/GroupByExpressionGatheringVisitor.java b/core/impl/src/main/java/com/blazebit/persistence/impl/GroupByExpressionGatheringVisitor.java index 9d1b806287..5f5c596123 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/GroupByExpressionGatheringVisitor.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/GroupByExpressionGatheringVisitor.java @@ -41,6 +41,11 @@ public void visit(ArrayExpression expression) { throw new IllegalArgumentException("At this point array expressions are not allowed anymore!"); } + @Override + public void visit(TreatExpression expression) { + handleExpression(expression); + } + @Override public void visit(PropertyExpression expression) { handleExpression(expression); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java index 850ebb3389..ea9d1bd8a1 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java @@ -43,9 +43,9 @@ import com.blazebit.persistence.impl.predicate.EqPredicate; import com.blazebit.persistence.impl.predicate.Predicate; import com.blazebit.persistence.impl.predicate.PredicateBuilder; +import com.sun.org.apache.xpath.internal.functions.Function; /** - * * @author Moritz Becker * @since 1.0 */ @@ -61,7 +61,7 @@ public class JoinManager extends AbstractManager { // root entity class private final String joinRestrictionKeyword; private final AliasManager aliasManager; - private final Metamodel metamodel; // needed for model-aware joins + private final EntityMetamodel metamodel; // needed for model-aware joins private final JoinManager parent; private final JoinOnBuilderEndedListener joinOnBuilderListener; private SubqueryInitiatorFactory subqueryInitFactory; @@ -75,7 +75,7 @@ public class JoinManager extends AbstractManager { JoinManager(MainQuery mainQuery, ResolvingQueryGenerator queryGenerator, AliasManager aliasManager, JoinManager parent, ExpressionFactory expressionFactory) { super(queryGenerator, mainQuery.parameterManager); this.aliasManager = aliasManager; - this.metamodel = mainQuery.em.getMetamodel(); + this.metamodel = mainQuery.metamodel; this.parent = parent; this.joinRestrictionKeyword = " " + mainQuery.jpaProvider.getOnClause() + " "; this.joinOnBuilderListener = new JoinOnBuilderEndedListener(); @@ -145,32 +145,39 @@ String addRoot(String correlationPath, String rootAlias) { return rootAlias; } - void removeRoot() { - // We only use this to remove implicit root nodes - JoinNode rootNode = rootNodes.remove(0); - aliasManager.unregisterAliasInfoForBottomLevel(rootNode.getAliasInfo()); - } + void removeRoot() { + // We only use this to remove implicit root nodes + JoinNode rootNode = rootNodes.remove(0); + aliasManager.unregisterAliasInfoForBottomLevel(rootNode.getAliasInfo()); + } JoinNode getRootNodeOrFail(String string) { - if (rootNodes.size() > 1) { - throw new IllegalArgumentException(string); - } - - return rootNodes.get(0); - } - - JoinNode getRootNode(String alias) { + if (rootNodes.size() > 1) { + throw new IllegalArgumentException(string); + } + + return rootNodes.get(0); + } + + JoinNode getRootNode(Expression expression) { + String alias; + if (expression instanceof PropertyExpression) { + alias = expression.toString(); + } else { + return null; + } + List nodes = rootNodes; int size = nodes.size(); for (int i = 0; i < size; i++) { JoinNode node = nodes.get(i); - if (alias.equals(node.getAliasInfo().getAlias())) { - return node; - } - } - - return null; - } + if (alias.equals(node.getAliasInfo().getAlias())) { + return node; + } + } + + return null; + } List getRoots() { return rootNodes; @@ -179,12 +186,12 @@ List getRoots() { boolean hasCollections() { List nodes = rootNodes; int size = nodes.size(); - for (int i = 0; i < size; i++) { - if (nodes.get(i).hasCollections()) { - return true; - } - } - + for (int i = 0; i < size; i++) { + if (nodes.get(i).hasCollections()) { + return true; + } + } + return false; } @@ -196,10 +203,10 @@ boolean hasJoins() { return true; } } - + return false; } - + Set getCollectionJoins() { if (rootNodes.isEmpty()) { return Collections.EMPTY_SET; @@ -211,7 +218,7 @@ Set getCollectionJoins() { return collectionJoins; } } - + private void fillCollectionJoinsNodesRec(JoinNode node, Set collectionNodes) { for (JoinTreeNode treeNode : node.getNodes().values()) { if (treeNode.isCollection()) { @@ -232,19 +239,19 @@ void setSubqueryInitFactory(SubqueryInitiatorFactory subqueryInitFactory) { } Set buildClause(StringBuilder sb, Set clauseExclusions, String aliasPrefix, boolean collectCollectionJoinNodes) { - collectionJoinNodes.clear(); + collectionJoinNodes.clear(); renderedJoins.clear(); sb.append(" FROM "); - + // TODO: we might have dependencies to other from clause elements which should also be accounted for List nodes = rootNodes; int size = nodes.size(); - for (int i = 0; i < size; i++) { - if (i != 0) { - sb.append(", "); - } - - JoinNode rootNode = nodes.get(i); + for (int i = 0; i < size; i++) { + if (i != 0) { + sb.append(", "); + } + + JoinNode rootNode = nodes.get(i); JoinNode correlationParent = rootNode.getCorrelationParent(); if (correlationParent != null) { @@ -256,20 +263,20 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St sb.append(type.getName()); } - sb.append(' '); - - if (aliasPrefix != null) { - sb.append(aliasPrefix); - } - - sb.append(rootNode.getAliasInfo().getAlias()); - - // TODO: not sure if needed since applyImplicitJoins will already invoke that - rootNode.registerDependencies(); - applyJoins(sb, rootNode.getAliasInfo(), rootNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes); - } - - return collectionJoinNodes; + sb.append(' '); + + if (aliasPrefix != null) { + sb.append(aliasPrefix); + } + + sb.append(rootNode.getAliasInfo().getAlias()); + + // TODO: not sure if needed since applyImplicitJoins will already invoke that + rootNode.registerDependencies(); + applyJoins(sb, rootNode.getAliasInfo(), rootNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes); + } + + return collectionJoinNodes; } void verifyBuilderEnded() { @@ -279,31 +286,31 @@ void verifyBuilderEnded() { void acceptVisitor(JoinNodeVisitor v) { List nodes = rootNodes; int size = nodes.size(); - for (int i = 0; i < size; i++) { - nodes.get(i).accept(v); - } + for (int i = 0; i < size; i++) { + nodes.get(i).accept(v); + } } public boolean acceptVisitor(AggregateDetectionVisitor aggregateDetector, boolean stopValue) { - Boolean stop = Boolean.valueOf(stopValue); + Boolean stop = Boolean.valueOf(stopValue); List nodes = rootNodes; int size = nodes.size(); - for (int i = 0; i < size; i++) { - if (stop.equals(nodes.get(i).accept(new AbortableOnClauseJoinNodeVisitor(aggregateDetector, stopValue)))) { - return true; - } - } - + for (int i = 0; i < size; i++) { + if (stop.equals(nodes.get(i).accept(new AbortableOnClauseJoinNodeVisitor(aggregateDetector, stopValue)))) { + return true; + } + } + return false; } void applyTransformer(ExpressionTransformer transformer) { List nodes = rootNodes; int size = nodes.size(); - for (int i = 0; i < size; i++) { - nodes.get(i).accept(new OnClauseJoinNodeVisitor(new PredicateManager.TransformationVisitor(transformer, ClauseType.JOIN))); - } + for (int i = 0; i < size; i++) { + nodes.get(i).accept(new OnClauseJoinNodeVisitor(new PredicateManager.TransformationVisitor(transformer, ClauseType.JOIN))); + } } private void renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNode node, String aliasPrefix) { @@ -355,7 +362,7 @@ private void renderReverseDependency(StringBuilder sb, JoinNode dependency, Stri for (JoinNode dep : dependency.getDependencies()) { if (markedJoinNodes.contains(dep)) { throw new IllegalStateException("Cyclic join dependency detected at absolute path [" - + dep.getAliasInfo().getAbsolutePath() + "] with alias [" + dep.getAliasInfo().getAlias() + "]"); + + dep.getAliasInfo().getAbsolutePath() + "] with alias [" + dep.getAliasInfo().getAlias() + "]"); } // render reverse dependencies renderReverseDependency(sb, dep, aliasPrefix); @@ -382,10 +389,10 @@ private void applyJoins(StringBuilder sb, JoinAliasInfo joinBase, Map JoinOnBuilder joinOn(X result, String path, String alias, JoinType type, } JoinNode join(String path, String alias, JoinType type, boolean fetch, boolean defaultJoin) { - Expression expr = expressionFactory.createPathExpression(path); - PathExpression pathExpression; + Expression expr = expressionFactory.createJoinPathExpression(path); + PathElementExpression elementExpr; + JoinResult result; + JoinNode current; if (expr instanceof PathExpression) { - pathExpression = (PathExpression) expr; + PathExpression pathExpression = (PathExpression) expr; + + if (isExternal(pathExpression) || isJoinableSelectAlias(pathExpression, false, false)) { + throw new IllegalArgumentException("No external path or select alias allowed in join path"); + } + + List pathElements = pathExpression.getExpressions(); + elementExpr = pathElements.get(pathElements.size() - 1); + result = implicitJoin(null, pathExpression, 0, pathElements.size() - 1); + current = result.baseNode; + } else if (expr instanceof TreatExpression) { + TreatExpression treatExpression = (TreatExpression) expr; + + if (isExternal(treatExpression)) { + throw new IllegalArgumentException("No external path or select alias allowed in join path"); + } + + Expression expression = treatExpression.getExpression(); + + if (expression instanceof PathExpression) { + PathExpression pathExpression = (PathExpression) expression; + List pathElements = pathExpression.getExpressions(); + elementExpr = pathElements.get(pathElements.size() - 1); + result = implicitJoin(null, pathExpression, 0, pathElements.size() - 1); + current = result.baseNode; + } else { + throw new IllegalArgumentException("Unexpected expression type[" + expression.getClass().getSimpleName() + "] in treat expression: " + treatExpression); + } } else { throw new IllegalArgumentException("Join path [" + path + "] is not a path"); } - if (isExternal(pathExpression) || isJoinableSelectAlias(pathExpression, false, false)) { - throw new IllegalArgumentException("No external path or select alias allowed in join path"); - } - - List pathElements = pathExpression.getExpressions(); - PathElementExpression elementExpr = pathElements.get(pathElements.size() - 1); - JoinResult result = implicitJoin(null, pathExpression, 0, pathElements.size() - 1); - JoinNode current = result.baseNode; - if (elementExpr instanceof ArrayExpression) { throw new IllegalArgumentException("Array expressions are not allowed!"); } else { @@ -492,15 +559,16 @@ JoinNode join(String path, String alias, JoinType type, boolean fetch, boolean d return result.baseNode; } - void implicitJoin(Expression expression, boolean objectLeafAllowed, ClauseType fromClause, boolean fromSubquery, boolean fromSelectAlias, boolean joinRequired) { - implicitJoin(expression, objectLeafAllowed, fromClause, fromSubquery, fromSelectAlias, joinRequired, false); + void implicitJoin(Expression expression, boolean objectLeafAllowed, String targetType, ClauseType fromClause, boolean fromSubquery, boolean fromSelectAlias, boolean joinRequired) { + implicitJoin(expression, objectLeafAllowed, targetType, fromClause, fromSubquery, fromSelectAlias, joinRequired, false); } - void implicitJoin(Expression expression, boolean objectLeafAllowed, ClauseType fromClause, boolean fromSubquery, boolean fromSelectAlias, boolean joinRequired, boolean fetch) { + void implicitJoin(Expression expression, boolean objectLeafAllowed, String targetTypeName, ClauseType fromClause, boolean fromSubquery, boolean fromSelectAlias, boolean joinRequired, boolean fetch) { PathExpression pathExpression; if (expression instanceof PathExpression) { pathExpression = (PathExpression) expression; + // If joinable select alias, it is guaranteed to have only a single element if (isJoinableSelectAlias(pathExpression, fromClause == ClauseType.SELECT, fromSubquery)) { String alias = pathExpression.getExpressions().get(0).toString(); Expression expr = ((SelectInfo) aliasManager.getAliasInfo(alias)).getExpression(); @@ -508,13 +576,13 @@ void implicitJoin(Expression expression, boolean objectLeafAllowed, ClauseType f // this check is necessary to prevent infinite recursion in the case of e.g. SELECT name AS name if (!fromSelectAlias) { // we have to do this implicit join because we might have to adjust the selectOnly flag in the referenced join nodes - implicitJoin(expr, true, fromClause, fromSubquery, true, joinRequired); + implicitJoin(expr, true, targetTypeName, fromClause, fromSubquery, true, joinRequired); } return; } else if (isExternal(pathExpression)) { // try to set base node and field for the external expression based // on existing joins in the super query - parent.implicitJoin(pathExpression, true, fromClause, true, fromSelectAlias, joinRequired); + parent.implicitJoin(pathExpression, true, targetTypeName, fromClause, true, fromSelectAlias, joinRequired); return; } @@ -524,20 +592,22 @@ void implicitJoin(Expression expression, boolean objectLeafAllowed, ClauseType f for (int i = 0; i < pathElementSize; i++) { PathElementExpression pathElem = pathElements.get(i); if (pathElem instanceof ArrayExpression) { - implicitJoin(((ArrayExpression) pathElem).getIndex(), false, fromClause, fromSubquery, fromSelectAlias, joinRequired); + implicitJoin(((ArrayExpression) pathElem).getIndex(), false, null, fromClause, fromSubquery, fromSelectAlias, joinRequired); } } PathElementExpression elementExpr = pathElements.get(pathElements.size() - 1); boolean singleValuedAssociationIdExpression = false; JoinNode current = null; + String currentTreatType = null; List resultFields = new ArrayList(); JoinResult currentResult; - JoinNode possibleRoot = getRootNode(pathElements.get(0).toString()); + JoinNode possibleRoot; int startIndex = 0; - if (possibleRoot != null) { + // Skip root speculation if this is just a single element path + if (pathElements.size() > 1 && (possibleRoot = getRootNode(pathElements.get(0))) != null) { startIndex = 1; current = possibleRoot; } @@ -547,7 +617,7 @@ void implicitJoin(Expression expression, boolean objectLeafAllowed, ClauseType f int maybeSingularAssociationIdIndex = pathElements.size() - 1; currentResult = implicitJoin(current, pathExpression, startIndex, maybeSingularAssociationIndex); current = currentResult.baseNode; - + if (currentResult.hasField()) { resultFields.addAll(Arrays.asList(currentResult.field.split("\\."))); } @@ -559,17 +629,18 @@ void implicitJoin(Expression expression, boolean objectLeafAllowed, ClauseType f // Start form the start index to respect the non joinable part currentResult = createOrUpdateNode(current, currentResult.field + "." + pathElements.get(maybeSingularAssociationIndex), null, null, true, true); current = currentResult.baseNode; - if(currentResult.hasField()){ + if (currentResult.hasField()) { currentResult = implicitJoin(current, pathExpression, startIndex, maybeSingularAssociationIdIndex); - }else{ + } else { currentResult = implicitJoin(current, pathExpression, maybeSingularAssociationIndex + 1, maybeSingularAssociationIdIndex); } resultFields.clear(); } else { currentResult = implicitJoin(current, pathExpression, maybeSingularAssociationIndex, maybeSingularAssociationIdIndex); } - + current = currentResult.baseNode; + currentTreatType = currentResult.typeName; if (currentResult.hasField()) { resultFields.addAll(Arrays.asList(currentResult.field.split("\\."))); @@ -578,6 +649,7 @@ void implicitJoin(Expression expression, boolean objectLeafAllowed, ClauseType f } else { currentResult = implicitJoin(current, pathExpression, startIndex, pathElements.size() - 1); current = currentResult.baseNode; + currentTreatType = currentResult.typeName; // TODO: Not sure if necessary if (currentResult.hasField()) { resultFields.addAll(Arrays.asList(currentResult.field.split("\\."))); @@ -592,6 +664,11 @@ void implicitJoin(Expression expression, boolean objectLeafAllowed, ClauseType f && (aliasInfo = aliasManager.getAliasInfoForBottomLevel(elementExpr.toString())) != null) { // No need to assert the resultFields here since they can't appear anyways if we enter this branch if (aliasInfo instanceof SelectInfo) { + if (targetTypeName != null) { + throw new IllegalArgumentException("The select alias '" + aliasInfo.getAlias() + + "' can not be used for a treat expression!."); + } + // We actually allow usage of select aliases in expressions, but JPA doesn't, so we have to resolve them here Expression selectExpr = ((SelectInfo) aliasInfo).getExpression(); @@ -601,13 +678,20 @@ void implicitJoin(Expression expression, boolean objectLeafAllowed, ClauseType f } // join the expression behind a select alias once when it is encountered the first time if (((PathExpression) selectExpr).getBaseNode() == null) { - implicitJoin(selectExpr, objectLeafAllowed, fromClause, fromSubquery, true, joinRequired); + implicitJoin(selectExpr, objectLeafAllowed, null, fromClause, fromSubquery, true, joinRequired); } PathExpression selectPathExpr = (PathExpression) selectExpr; result = new JoinResult((JoinNode) selectPathExpr.getBaseNode(), selectPathExpr.getField()); } else { - // Naked join alias usage like in "KEY(joinAlias)" - result = new JoinResult(((JoinAliasInfo) aliasInfo).getJoinNode(), null); + JoinNode pathJoinNode = ((JoinAliasInfo) aliasInfo).getJoinNode(); + if (targetTypeName != null) { + // Treated root path + ManagedType targetType = metamodel.managedType(targetTypeName); + result = new JoinResult(pathJoinNode, null, targetTypeName); + } else { + // Naked join alias usage like in "KEY(joinAlias)" + result = new JoinResult(pathJoinNode, null); + } } } else { // current might be null @@ -679,7 +763,7 @@ void implicitJoin(Expression expression, boolean objectLeafAllowed, ClauseType f result = new JoinResult(current, null); } else if (!pathExpression.isUsedInCollectionFunction()) { if (resultFields.isEmpty()) { - result = implicitJoinSingle(current, elementExpr.toString(), objectLeafAllowed, joinRequired); + result = implicitJoinSingle(current, currentTreatType, elementExpr.toString(), objectLeafAllowed, joinRequired); } else { resultFields.add(elementExpr.toString()); @@ -688,7 +772,7 @@ void implicitJoin(Expression expression, boolean objectLeafAllowed, ClauseType f + StringUtils.join(".", resultFields) + "]"); } - result = implicitJoinSingle(current, StringUtils.join(".", resultFields), objectLeafAllowed, joinRequired); + result = implicitJoinSingle(current, currentTreatType, StringUtils.join(".", resultFields), objectLeafAllowed, joinRequired); } } else { if (resultFields.isEmpty()) { @@ -716,41 +800,50 @@ void implicitJoin(Expression expression, boolean objectLeafAllowed, ClauseType f } if (result.isLazy()) { - pathExpression.setPathReference(new LazyPathReference(result.baseNode, result.field)); + pathExpression.setPathReference(new LazyPathReference(result.baseNode, result.field, result.typeName)); } else { - pathExpression.setPathReference(new SimplePathReference(result.baseNode, result.field)); + pathExpression.setPathReference(new SimplePathReference(result.baseNode, result.field, result.typeName)); + } + + if (result.hasTreatedSubpath) { + pathExpression.setHasTreatedSubpath(true); } } else if (expression instanceof CompositeExpression) { List expressions = ((CompositeExpression) expression).getExpressions(); int size = expressions.size(); for (int i = 0; i < size; i++) { - implicitJoin(expressions.get(i), objectLeafAllowed, fromClause, fromSubquery, fromSelectAlias, joinRequired); + implicitJoin(expressions.get(i), objectLeafAllowed, null, fromClause, fromSubquery, fromSelectAlias, joinRequired); } } else if (expression instanceof FunctionExpression) { List expressions = ((FunctionExpression) expression).getExpressions(); int size = expressions.size(); for (int i = 0; i < size; i++) { - implicitJoin(expressions.get(i), objectLeafAllowed, fromClause, fromSubquery, fromSelectAlias, joinRequired); + implicitJoin(expressions.get(i), objectLeafAllowed, null, fromClause, fromSubquery, fromSelectAlias, joinRequired); } + } else if (expression instanceof ArrayExpression || expression instanceof GeneralCaseExpression || expression instanceof TreatExpression) { + // NOTE: I haven't found a use case for this yet, so I'd like to throw an exception instead of silently not supporting this + throw new IllegalArgumentException("Unsupported expression type for implicit joining found: " + expression.getClass()); } } - + private static class LazyPathReference implements PathReference { private final JoinNode baseNode; private final String field; - - public LazyPathReference(JoinNode baseNode, String field) { + private final String typeName; + + public LazyPathReference(JoinNode baseNode, String field, String typeName) { this.baseNode = baseNode; this.field = field; + this.typeName = typeName; } - + @Override public JoinNode getBaseNode() { JoinTreeNode subNode = baseNode.getNodes().get(field); if (subNode != null && subNode.getDefaultNode() != null) { return subNode.getDefaultNode(); } - + return baseNode; } @@ -759,17 +852,23 @@ public String getField() { JoinTreeNode subNode = baseNode.getNodes().get(field); if (subNode != null && subNode.getDefaultNode() != null) { return null; - } - + } + return field; } + @Override + public String getTreatTypeName() { + return typeName; + } + @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((baseNode == null) ? 0 : baseNode.hashCode()); result = prime * result + ((field == null) ? 0 : field.hashCode()); + result = prime * result + ((typeName == null) ? 0 : typeName.hashCode()); return result; } @@ -792,11 +891,16 @@ public boolean equals(Object obj) { return false; } else if (!field.equals(other.getField())) return false; + if (typeName == null) { + if (other.getTreatTypeName() != null) + return false; + } else if (!typeName.equals(other.getTreatTypeName())) + return false; return true; } } - private boolean validPath(Class currentClass, List pathElements) { + private boolean validPath(Class currentClass, List pathElements) { for (int i = 0; i < pathElements.size(); i++) { String element = pathElements.get(i); ManagedType t = metamodel.managedType(currentClass); @@ -852,12 +956,12 @@ private boolean isSingleValuedAssociationId(JoinResult joinResult, List parentClass = parent.getPropertyClass(); baseType = metamodel.managedType(parentClass); - + if (joinResult.hasField()) { Attribute fieldAttribute = JpaUtils.getAttribute(baseType, joinResult.field); baseType = metamodel.managedType(fieldAttribute.getJavaType()); } - + maybeSingularAssociationAttributes = JpaUtils.getAttributesPolymorphic(metamodel, baseType, maybeSingularAssociationName); } @@ -867,9 +971,9 @@ private boolean isSingleValuedAssociationId(JoinResult joinResult, List maybeSingularAssociation : maybeSingularAssociationAttributes) { if (maybeSingularAssociation.getPersistentAttributeType() != Attribute.PersistentAttributeType.MANY_TO_ONE - // TODO: to be able to support ONE_TO_ONE we need to know where the FK is - // && maybeSingularAssociation.getPersistentAttributeType() != Attribute.PersistentAttributeType.ONE_TO_ONE - ) { + // TODO: to be able to support ONE_TO_ONE we need to know where the FK is + // && maybeSingularAssociation.getPersistentAttributeType() != Attribute.PersistentAttributeType.ONE_TO_ONE + ) { return false; } @@ -923,7 +1027,7 @@ private String getJoinAlias(ArrayExpression expr) { sb.append('_'); sb.append(indexPathExpr.getField().replaceAll("\\.", "_")); } - } else if (indexExpr instanceof FooExpression){ + } else if (indexExpr instanceof FooExpression) { sb.append('_'); sb.append(indexExpr.toString().replaceAll("\\.", "_")); } else if (indexExpr instanceof NumericLiteral) { @@ -939,7 +1043,7 @@ private String getJoinAlias(ArrayExpression expr) { private EqPredicate getArrayExpressionPredicate(JoinNode joinNode, ArrayExpression arrayExpr) { PathExpression keyPath = new PathExpression(new ArrayList(), true); keyPath.getExpressions().add(new PropertyExpression(joinNode.getAliasInfo().getAlias())); - keyPath.setPathReference(new SimplePathReference(joinNode, null)); + keyPath.setPathReference(new SimplePathReference(joinNode, null, null)); FunctionExpression keyExpression = new FunctionExpression("KEY", Arrays.asList((Expression) keyPath)); return new EqPredicate(keyExpression, arrayExpr.getIndex()); } @@ -954,6 +1058,7 @@ public void visit(PathExpression pathExpr) { joinNode.getDependencies().add((JoinNode) pathExpr.getBaseNode()); } } + }); } @@ -979,11 +1084,13 @@ private void generateAndApplyOnPredicate(JoinNode joinNode, ArrayExpression arra private JoinResult implicitJoin(JoinNode current, PathExpression pathExpression, int start, int end) { List pathElements = pathExpression.getExpressions(); List resultFields = new ArrayList(); + String currentTargetType = null; PathElementExpression elementExpr; for (int i = start; i < end; i++) { AliasInfo aliasInfo; elementExpr = pathElements.get(i); + if (elementExpr instanceof ArrayExpression) { ArrayExpression arrayExpr = (ArrayExpression) elementExpr; String joinRelationName = arrayExpr.getBase().toString(); @@ -1011,6 +1118,29 @@ private JoinResult implicitJoin(JoinNode current, PathExpression pathExpression, } generateAndApplyOnPredicate(current, arrayExpr); } + + // Reset target type + currentTargetType = null; + } else if (elementExpr instanceof TreatExpression) { + if (i != 0 || current != null) { + throw new IllegalArgumentException("A treat expression should be the first element in a path!"); + } + TreatExpression treatExpression = (TreatExpression) elementExpr; + boolean fromSubquery = false; + boolean fromSelectAlias = false; + boolean joinRequired = false; + boolean fetch = false; + + // TODO: reuse existing treated join node or create one? not sure if it wasn't better to just pass it through to the persistence provider + if (treatExpression.getExpression() instanceof PathExpression) { + PathExpression treatedPathExpression = (PathExpression) treatExpression.getExpression(); + implicitJoin(treatedPathExpression, true, treatExpression.getType(), null, fromSubquery, fromSelectAlias, true, fetch); + JoinNode treatedJoinNode = (JoinNode) treatedPathExpression.getBaseNode(); + current = treatedJoinNode; + currentTargetType = treatExpression.getType(); + } else { + throw new UnsupportedOperationException("Unsupported treated expression type: " + treatExpression.getExpression().getClass()); + } } else if (pathElements.size() == 1 && (aliasInfo = aliasManager.getAliasInfoForBottomLevel(elementExpr.toString())) != null) { if (aliasInfo instanceof SelectInfo) { throw new IllegalArgumentException("Can't dereference a select alias"); @@ -1018,30 +1148,34 @@ private JoinResult implicitJoin(JoinNode current, PathExpression pathExpression, // Join alias usage like in "joinAlias.relationName" current = ((JoinAliasInfo) aliasInfo).getJoinNode(); } + // Reset target type + currentTargetType = null; } else { if (!resultFields.isEmpty()) { resultFields.add(elementExpr.toString()); JoinResult currentResult = createOrUpdateNode(current, StringUtils.join(".", resultFields), null, null, true, true); current = currentResult.baseNode; resultFields.clear(); - if(currentResult.hasField()){ + if (currentResult.hasField()) { resultFields.add(currentResult.field); } } else { final JoinResult result = implicitJoinSingle(current, elementExpr.toString()); current = result.baseNode; - + if (result.hasField()) { resultFields.add(result.field); } } + // Reset target type + currentTargetType = null; } } if (resultFields.isEmpty()) { - return new JoinResult(current, null); + return new JoinResult(current, null, currentTargetType); } else { - return new JoinResult(current, StringUtils.join(".", resultFields)); + return new JoinResult(current, StringUtils.join(".", resultFields), currentTargetType); } } @@ -1064,31 +1198,45 @@ private JoinResult implicitJoinSingle(JoinNode baseNode, String attributeName) { return createOrUpdateNode(baseNode, attributeName, null, null, true, true); } - private JoinResult implicitJoinSingle(JoinNode baseNode, String attributeName, boolean objectLeafAllowed, boolean joinRequired) { + private JoinResult implicitJoinSingle(JoinNode baseNode, String treatTypeName, String attributeName, boolean objectLeafAllowed, boolean joinRequired) { JoinNode newBaseNode; String field; + boolean hasTreatedSubpath = false; boolean lazy = false; // The given path may be relative to the root or it might be an alias if (objectLeafAllowed) { - Class baseNodeType = baseNode.getPropertyClass(); - Attribute attr = getSimpleAttributeForImplicitJoining(metamodel.managedType(baseNodeType), attributeName); + Class baseNodeClass = baseNode.getPropertyClass(); + String typeName; + ManagedType baseNodeType; + + if (treatTypeName != null) { + typeName = treatTypeName; + baseNodeType = metamodel.managedType(treatTypeName); + } else { + typeName = baseNodeClass.getSimpleName(); + baseNodeType = metamodel.managedType(baseNodeClass); + } + + Attribute attr = getSimpleAttributeForImplicitJoining(baseNodeType, attributeName); if (attr == null) { - throw new IllegalArgumentException("Field with name " + attributeName + " was not found within class " + baseNodeType.getName()); + throw new IllegalArgumentException("Field with name " + attributeName + " was not found within managed type " + typeName); } - + if (joinRequired || attr.isCollection()) { - final JoinResult newBaseNodeResult = implicitJoinSingle(baseNode, attributeName); - newBaseNode = newBaseNodeResult.baseNode; - // check if the last path element was also joined - if (newBaseNode != baseNode) { - field = null; - } else { - field = attributeName; - } + final JoinResult newBaseNodeResult = implicitJoinSingle(baseNode, attributeName); + newBaseNode = newBaseNodeResult.baseNode; + // check if the last path element was also joined + if (newBaseNode != baseNode) { + field = null; + } else { + hasTreatedSubpath = treatTypeName != null; + field = attributeName; + } } else { - newBaseNode = baseNode; + newBaseNode = baseNode; field = attributeName; lazy = true; + hasTreatedSubpath = treatTypeName != null; } } else { Class baseNodeType = baseNode.getPropertyClass(); @@ -1102,7 +1250,7 @@ private JoinResult implicitJoinSingle(JoinNode baseNode, String attributeName, b newBaseNode = baseNode; field = attributeName; } - return new JoinResult(newBaseNode, field, lazy); + return new JoinResult(newBaseNode, field, lazy, hasTreatedSubpath); } private Attribute getSimpleAttributeForImplicitJoining(ManagedType type, String attributeName) { @@ -1114,7 +1262,7 @@ private JoinResult implicitJoinSingle(JoinNode baseNode, String attributeName, b String[] attributeParts = attributeName.split("\\."); attr = getPolymorphicSimpleAttributeForImplicitJoining(type, attributeParts[0]); - + for (int i = 1; i < attributeParts.length; i++) { type = metamodel.managedType(JpaUtils.resolveFieldClass(type.getJavaType(), attr)); attr = getPolymorphicAttributeForJoining(type, attributeParts[i]); @@ -1145,7 +1293,7 @@ private JoinResult implicitJoinSingle(JoinNode baseNode, String attributeName, b } else { for (Attribute a : amiguousAttributes) { LOG.warning("The attribute [" + attributeName + "] of the class [" + a.getDeclaringType().getJavaType().getName() - + "] is ambiguous for polymorphic implicit joining on the type [" + type.getJavaType().getName() + "]"); + + "] is ambiguous for polymorphic implicit joining on the type [" + type.getJavaType().getName() + "]"); } return simpleAttribute; @@ -1176,17 +1324,17 @@ private JoinType getModelAwareType(JoinNode baseNode, Attribute attr) { } if ((attr.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE || attr.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE) - && ((SingularAttribute) attr).isOptional() == false) { + && ((SingularAttribute) attr).isOptional() == false) { return JoinType.INNER; } else { return JoinType.LEFT; } } - + private static class AttributeJoinResult { private final Attribute attribute; private final Class containingClass; - + public AttributeJoinResult(Attribute attribute, Class containingClass) { this.attribute = attribute; this.containingClass = containingClass; @@ -1202,7 +1350,7 @@ private AttributeJoinResult getAttributeForJoining(ManagedType type, String a String[] attributeParts = attributeName.split("\\."); attr = getPolymorphicAttributeForJoining(type, attributeParts[0]); - + for (int i = 1; i < attributeParts.length; i++) { type = metamodel.managedType(JpaUtils.resolveFieldClass(type.getJavaType(), attr)); attr = getPolymorphicAttributeForJoining(type, attributeParts[i]); @@ -1210,7 +1358,7 @@ private AttributeJoinResult getAttributeForJoining(ManagedType type, String a return new AttributeJoinResult(attr, type.getJavaType()); } - + private Attribute getPolymorphicAttributeForJoining(ManagedType type, String attributeName) { Set> resolvedAttributes = JpaUtils.getAttributesPolymorphic(metamodel, type, attributeName); Iterator> iter = resolvedAttributes.iterator(); @@ -1227,8 +1375,8 @@ private AttributeJoinResult getAttributeForJoining(ManagedType type, String a if (JpaUtils.isJoinable(attr)) { if (joinableAttribute != null && !joinableAttribute.getJavaType().equals(attr.getJavaType())) { throw new IllegalArgumentException("Multiple joinable attributes with the name [" + attributeName - + "] but different java types in the types [" + joinableAttribute.getDeclaringType().getJavaType().getName() - + "] and [" + attr.getDeclaringType().getJavaType().getName() + "] found!"); + + "] but different java types in the types [" + joinableAttribute.getDeclaringType().getJavaType().getName() + + "] and [" + attr.getDeclaringType().getJavaType().getName() + "] found!"); } else { joinableAttribute = attr; } @@ -1261,10 +1409,10 @@ private JoinResult createOrUpdateNode(JoinNode baseNode, String joinRelationName if (!JpaUtils.isJoinable(attr)) { if (LOG.isLoggable(Level.FINE)) { LOG.fine(new StringBuilder("Field with name ").append(joinRelationName) - .append(" of class ") - .append(baseNodeType.getName()) - .append(" is parseable and therefore it has not to be fetched explicitly.") - .toString()); + .append(" of class ") + .append(baseNodeType.getName()) + .append(" is parseable and therefore it has not to be fetched explicitly.") + .toString()); } return new JoinResult(baseNode, joinRelationName); } @@ -1294,7 +1442,7 @@ private void checkAliasIsAvailable(String alias, String currentJoinPath, String throw new IllegalArgumentException(errorMessage); } else { throw new RuntimeException("Probably a programming error if this happens. An alias[" + alias + "] for the same join path[" - + currentJoinPath + "] is available but the join node is not!"); + + currentJoinPath + "] is available but the join node is not!"); } } } @@ -1336,7 +1484,7 @@ private JoinNode getOrCreate(JoinNode baseNode, String joinRelationName, Class) expression.getSubquery()).applyImplicitJoins(); - } - - public boolean isJoinWithObjectLeafAllowed() { - return joinWithObjectLeafAllowed; - } - - public void setJoinWithObjectLeafAllowed(boolean joinWithObjectLeafAllowed) { - this.joinWithObjectLeafAllowed = joinWithObjectLeafAllowed; - } - - @Override - public void visit(EqPredicate predicate) { - boolean original = joinRequired; - joinRequired = false; - predicate.getLeft().accept(this); - predicate.getRight().accept(this); - joinRequired = original; - } - - @Override - public void visit(IsNullPredicate predicate) { - boolean original = joinRequired; - joinRequired = false; - predicate.getExpression().accept(this); - joinRequired = original; - } - - @Override - public void visit(IsEmptyPredicate predicate) { - boolean original = joinRequired; - joinRequired = false; - predicate.getExpression().accept(this); - joinRequired = original; - } - - @Override - public void visit(MemberOfPredicate predicate) { - boolean original = joinRequired; - joinRequired = false; - predicate.getLeft().accept(this); - predicate.getRight().accept(this); - joinRequired = original; - } - - @Override - public void visit(InPredicate predicate) { - boolean original = joinRequired; - joinRequired = false; - predicate.getLeft().accept(this); - predicate.getRight().accept(this); - joinRequired = original; - } -} +/* + * Copyright 2014 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.impl; + +import com.blazebit.persistence.impl.expression.*; +import com.blazebit.persistence.impl.predicate.EqPredicate; +import com.blazebit.persistence.impl.predicate.InPredicate; +import com.blazebit.persistence.impl.predicate.IsEmptyPredicate; +import com.blazebit.persistence.impl.predicate.IsNullPredicate; +import com.blazebit.persistence.impl.predicate.MemberOfPredicate; + +/** + * + * @author Moritz Becker + * @since 1.0 + */ +public class JoinVisitor extends VisitorAdapter { + + private final JoinManager joinManager; + private boolean joinRequired; + private boolean joinWithObjectLeafAllowed = true; + private ClauseType fromClause; + + public JoinVisitor(JoinManager joinManager) { + this.joinManager = joinManager; + // By default we require joins + this.joinRequired = true; + } + + public ClauseType getFromClause() { + return fromClause; + } + + public void setFromClause(ClauseType fromClause) { + this.fromClause = fromClause; + } + + @Override + public void visit(PathExpression expression) { + joinManager.implicitJoin(expression, joinWithObjectLeafAllowed, null, fromClause, false, false, joinRequired); + } + + @Override + public void visit(TreatExpression expression) { + throw new IllegalArgumentException("Treat should not be a root of an expression: " + expression.toString()); + } + + public boolean isJoinRequired() { + return joinRequired; + } + + public void setJoinRequired(boolean joinRequired) { + this.joinRequired = joinRequired; + } + + @Override + public void visit(FunctionExpression expression) { + // do not join outer expressions + if (!ExpressionUtils.isOuterFunction(expression)) { + super.visit(expression); + } + } + + // Added eager initialization of subqueries + @Override + public void visit(SubqueryExpression expression) { + // TODO: we have to pass the fromClause into the subquery so that joins, generated by OUTERs from the subquery don't get + // rendered if not needed + // TODO: this is ugly + ((AbstractCommonQueryBuilder) expression.getSubquery()).applyImplicitJoins(); + } + + public boolean isJoinWithObjectLeafAllowed() { + return joinWithObjectLeafAllowed; + } + + public void setJoinWithObjectLeafAllowed(boolean joinWithObjectLeafAllowed) { + this.joinWithObjectLeafAllowed = joinWithObjectLeafAllowed; + } + + @Override + public void visit(EqPredicate predicate) { + boolean original = joinRequired; + joinRequired = false; + predicate.getLeft().accept(this); + predicate.getRight().accept(this); + joinRequired = original; + } + + @Override + public void visit(IsNullPredicate predicate) { + boolean original = joinRequired; + joinRequired = false; + predicate.getExpression().accept(this); + joinRequired = original; + } + + @Override + public void visit(IsEmptyPredicate predicate) { + boolean original = joinRequired; + joinRequired = false; + predicate.getExpression().accept(this); + joinRequired = original; + } + + @Override + public void visit(MemberOfPredicate predicate) { + boolean original = joinRequired; + joinRequired = false; + predicate.getLeft().accept(this); + predicate.getRight().accept(this); + joinRequired = original; + } + + @Override + public void visit(InPredicate predicate) { + boolean original = joinRequired; + joinRequired = false; + predicate.getLeft().accept(this); + predicate.getRight().accept(this); + joinRequired = original; + } +} diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/MainQuery.java b/core/impl/src/main/java/com/blazebit/persistence/impl/MainQuery.java index 87deeec246..91a24edb94 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/MainQuery.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/MainQuery.java @@ -15,6 +15,7 @@ public class MainQuery { final CriteriaBuilderFactoryImpl cbf; final EntityManager em; + final EntityMetamodel metamodel; final JpaProvider jpaProvider; final DbmsDialect dbmsDialect; final Set registeredFunctions; @@ -26,6 +27,7 @@ private MainQuery(CriteriaBuilderFactoryImpl cbf, EntityManager em, JpaProvider super(); this.cbf = cbf; this.em = em; + this.metamodel = cbf.getMetamodel(); this.jpaProvider = jpaProvider; this.dbmsDialect = dbmsDialect; this.registeredFunctions = registeredFunctions; diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/OuterFunctionTransformer.java b/core/impl/src/main/java/com/blazebit/persistence/impl/OuterFunctionTransformer.java index 6c2824d876..b61b77b3d0 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/OuterFunctionTransformer.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/OuterFunctionTransformer.java @@ -74,7 +74,7 @@ public Expression transform(Expression original, ClauseType fromClause, boolean PathExpression path = (PathExpression) ((FunctionExpression) original).getExpressions().get(0); if (joinManager.getParent() != null) { - joinManager.getParent().implicitJoin(path, true, fromClause, false, true, joinRequired, false); + joinManager.getParent().implicitJoin(path, true, null, fromClause, false, true, joinRequired, false); } return original; diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/PredicateManager.java b/core/impl/src/main/java/com/blazebit/persistence/impl/PredicateManager.java index 54cee327e9..b4bf5915cd 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/PredicateManager.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/PredicateManager.java @@ -214,6 +214,11 @@ void applyPredicate(ResolvingQueryGenerator queryGenerator) { } // TODO: needs equals-hashCode implementation + + /** + * The transformation visitor's job is to let expressions from predicates be replaced by results of transformers. + * Since it is only ever applied to a predicate, we don't need to consider the visit methods for expressions. + */ static class TransformationVisitor extends VisitorAdapter { private final ExpressionTransformer transformer; diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/ResolvingQueryGenerator.java b/core/impl/src/main/java/com/blazebit/persistence/impl/ResolvingQueryGenerator.java index 3e080de764..4074ffc8ac 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/ResolvingQueryGenerator.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/ResolvingQueryGenerator.java @@ -21,15 +21,7 @@ import java.util.Set; import com.blazebit.persistence.BaseFinalSetOperationBuilder; -import com.blazebit.persistence.impl.expression.AggregateExpression; -import com.blazebit.persistence.impl.expression.ArrayExpression; -import com.blazebit.persistence.impl.expression.Expression; -import com.blazebit.persistence.impl.expression.FooExpression; -import com.blazebit.persistence.impl.expression.FunctionExpression; -import com.blazebit.persistence.impl.expression.NullExpression; -import com.blazebit.persistence.impl.expression.ParameterExpression; -import com.blazebit.persistence.impl.expression.PathExpression; -import com.blazebit.persistence.impl.expression.SubqueryExpression; +import com.blazebit.persistence.impl.expression.*; import com.blazebit.persistence.impl.jpaprovider.HibernateJpaProvider; import com.blazebit.persistence.impl.jpaprovider.JpaProvider; import com.blazebit.persistence.spi.OrderByElement; @@ -218,6 +210,18 @@ private boolean isCountStarFunction(FunctionExpression expression) { && "COUNT".equalsIgnoreCase(expression.getFunctionName()); } + @Override + public void visit(TreatExpression expression) { + if (jpaProvider.supportsRootTreat()) { + super.visit(expression); + } else if (jpaProvider.supportsSubtypePropertyResolving()) { + // NOTE: this might be wrong when having multiple same named properties + expression.getExpression().accept(this); + } else { + throw new IllegalArgumentException("Can not render treat expression[" + expression.toString() + "] as the JPA provider does not support it!"); + } + } + @Override public void visit(PathExpression expression) { if (resolveSelectAliases) { @@ -259,24 +263,29 @@ public void visit(PathExpression expression) { sb.append(')'); } } else { - // Dereferencing after a value function does not seem to work for datanucleus? -// boolean valueFunction = false; - boolean valueFunction = needsValueFunction(expression) && jpaProvider.getCollectionValueFunction() != null; - - if (valueFunction) { - sb.append(jpaProvider.getCollectionValueFunction()); - sb.append('('); - } + if (expression.hasTreatedSubpath()) { + // Actually we know that the treated subpath must be the first part of the path + expression.getExpressions().get(0).accept(this); + } else { + // Dereferencing after a value function does not seem to work for datanucleus? + // boolean valueFunction = false; + boolean valueFunction = needsValueFunction(expression) && jpaProvider.getCollectionValueFunction() != null; + + if (valueFunction) { + sb.append(jpaProvider.getCollectionValueFunction()); + sb.append('('); + } - if (aliasPrefix != null) { - sb.append(aliasPrefix); - } + if (aliasPrefix != null) { + sb.append(aliasPrefix); + } - JoinNode baseNode = (JoinNode) expression.getBaseNode(); - sb.append(baseNode.getAliasInfo().getAlias()); + JoinNode baseNode = (JoinNode) expression.getBaseNode(); + sb.append(baseNode.getAliasInfo().getAlias()); - if (valueFunction) { - sb.append(')'); + if (valueFunction) { + sb.append(')'); + } } sb.append(".").append(expression.getField()); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/SelectManager.java b/core/impl/src/main/java/com/blazebit/persistence/impl/SelectManager.java index b20f674780..7189aed039 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/SelectManager.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/SelectManager.java @@ -190,7 +190,7 @@ void buildGroupByClauses(final Metamodel m, Set clauses) { String rootAlias = rootNode.getAliasInfo().getAlias(); List path = Arrays.asList((PathElementExpression) new PropertyExpression(rootAlias)); - resolveVisitor.visit(new PathExpression(path, new SimplePathReference(rootNode, null), false, false)); + resolveVisitor.visit(new PathExpression(path, new SimplePathReference(rootNode, null, null), false, false)); for (PathExpression pathExpr : componentPaths) { sb.setLength(0); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/SizeTransformationVisitor.java b/core/impl/src/main/java/com/blazebit/persistence/impl/SizeTransformationVisitor.java index 3b428326b3..118371d601 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/SizeTransformationVisitor.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/SizeTransformationVisitor.java @@ -197,7 +197,7 @@ public Expression visit(FunctionExpression expression) { pathElementExpr.add(new PropertyExpression(rootAlias)); pathElementExpr.add(new PropertyExpression(rootId)); PathExpression groupByExpr = new PathExpression(pathElementExpr); - joinManager.implicitJoin(groupByExpr, true, null, false, false, false); + joinManager.implicitJoin(groupByExpr, true, null, null, false, false, false); if (groupByManager.hasGroupBys() && !groupByManager.existsGroupBy(groupByExpr)) { return generateSubquery(sizeArg, startClass); @@ -212,11 +212,11 @@ public Expression visit(FunctionExpression expression) { String nodeLookupKey = originalNode.getAliasInfo().getAbsolutePath() + "." + sizeArg.getField(); PathReference generatedJoin = generatedJoins.get(nodeLookupKey); if (generatedJoin == null) { - joinManager.implicitJoin(sizeArg, true, clause, false, false, true); + joinManager.implicitJoin(sizeArg, true, null, clause, false, false, true); generatedJoin = sizeArg.getPathReference(); generatedJoins.put(((JoinNode) generatedJoin.getBaseNode()).getAliasInfo().getAbsolutePath(), generatedJoin); } else { - sizeArg.setPathReference(new SimplePathReference(generatedJoin.getBaseNode(), generatedJoin.getField())); + sizeArg.setPathReference(new SimplePathReference(generatedJoin.getBaseNode(), generatedJoin.getField(), null)); } if (distinctRequired == false) { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/DataNucleusJpaProvider.java b/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/DataNucleusJpaProvider.java index 82504ec41e..c76bc69d85 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/DataNucleusJpaProvider.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/DataNucleusJpaProvider.java @@ -93,4 +93,13 @@ public String getCustomFunctionInvocation(String functionName, int argumentCount return functionName + "("; } + @Override + public boolean supportsRootTreat() { + return false; + } + + @Override + public boolean supportsSubtypePropertyResolving() { + return true; + } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/EclipseLinkJpaProvider.java b/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/EclipseLinkJpaProvider.java index 6cd6073b4f..31980087f1 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/EclipseLinkJpaProvider.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/EclipseLinkJpaProvider.java @@ -97,4 +97,14 @@ public String getCustomFunctionInvocation(String functionName, int argumentCount return "OPERATOR('" + functionName + "',"; } + @Override + public boolean supportsRootTreat() { + return true; + } + + @Override + public boolean supportsSubtypePropertyResolving() { + return false; + } + } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/HibernateJpaProvider.java b/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/HibernateJpaProvider.java index bbadefd26f..f9094ca36b 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/HibernateJpaProvider.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/HibernateJpaProvider.java @@ -139,4 +139,14 @@ public String getCustomFunctionInvocation(String functionName, int argumentCount return functionName + "("; } + @Override + public boolean supportsRootTreat() { + return false; + } + + @Override + public boolean supportsSubtypePropertyResolving() { + return true; + } + } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/JpaProvider.java b/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/JpaProvider.java index fc210e6054..37dfe7e024 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/JpaProvider.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/jpaprovider/JpaProvider.java @@ -45,4 +45,8 @@ public interface JpaProvider { public boolean supportsNullPrecedenceExpression(); public void renderNullPrecedence(StringBuilder sb, String expression, String resolvedExpression, String order, String nulls); + + public boolean supportsRootTreat(); + + public boolean supportsSubtypePropertyResolving(); } diff --git a/core/parser/src/main/antlr4/com/blazebit/persistence/parser/JPQLSelectExpression.g4 b/core/parser/src/main/antlr4/com/blazebit/persistence/parser/JPQLSelectExpression.g4 index 2254970eaf..c5a97cfd24 100644 --- a/core/parser/src/main/antlr4/com/blazebit/persistence/parser/JPQLSelectExpression.g4 +++ b/core/parser/src/main/antlr4/com/blazebit/persistence/parser/JPQLSelectExpression.g4 @@ -22,10 +22,12 @@ import JPQL_lexer; private boolean allowOuter = false; private boolean allowCaseWhen = false; private boolean allowQuantifiedPredicates = false; -public JPQLSelectExpressionParser(TokenStream input, boolean allowCaseWhen, boolean allowQuantifiedPredicates){ +private boolean allowTreatJoinExtension = false; +public JPQLSelectExpressionParser(TokenStream input, boolean allowCaseWhen, boolean allowQuantifiedPredicates, boolean allowTreatJoinExtension){ this(input); this.allowCaseWhen = allowCaseWhen; this.allowQuantifiedPredicates = allowQuantifiedPredicates; + this.allowTreatJoinExtension = allowTreatJoinExtension; } } @@ -39,6 +41,25 @@ parsePath : state_field_path_expression EOF | single_element_path_expression EOF ; +parseJoinPath : join_association_path_expression EOF + ; + +join_association_path_expression + : simple_subpath '.' general_path_element #SimpleJoinPathExpression + // TODO: I am not sure this is actually necessary. Maybe we leave it in the grammar but the treated_subpath will be replaced by a join node + | {allowTreatJoinExtension == true}? treated_subpath '.' general_path_element #ExtendedJoinPathExpression + | single_element_path_expression #SingleJoinElementExpression + | TREAT '(' join_path_expression AS subtype ')' #TreatJoinPathExpression + ; + +join_path_expression + : simple_subpath '.' general_path_element #SimplePath + | {allowTreatJoinExtension == true}? TREAT '(' identifier AS subtype ')' '.' simple_subpath #TreatedRootPath + ; + +subtype : identifier + ; + parseSimpleExpression : simple_expression EOF ; @@ -78,13 +99,20 @@ key_value_expression : name=KEY '('collection_valued_path_expression')' | name=VALUE '('collection_valued_path_expression')' ; +treated_key_value_expression + : + // I think this is an error in the JPQL grammar as it doesn't make sense to do TREAT(ENTRY(...)) + //| TREAT '(' qualified_identification_variable AS subtype ')' + | TREAT '(' key_value_expression AS subtype ')' + ; + qualified_identification_variable : name=ENTRY '('collection_valued_path_expression')' # EntryFunction | key_value_expression #KeyValueExpression ; single_valued_path_expression : qualified_identification_variable - // TODO: Add treat here + | treated_key_value_expression | state_field_path_expression | single_element_path_expression ; @@ -113,7 +141,14 @@ array_expression : simple_path_element '[' Input_parameter ']' #ArrayExpressionP -general_subpath : general_path_start('.'general_path_element)* +general_subpath : simple_subpath + | treated_subpath + ; + +simple_subpath : general_path_start ('.'general_path_element)* + ; + +treated_subpath : TREAT '(' general_subpath AS subtype ')' ('.'general_path_element)* ; state_field_path_expression : path @@ -485,6 +520,8 @@ keyword :KEY | EMPTY | MEMBER | OF + | TREAT + | AS | Outer_function ; diff --git a/core/parser/src/main/antlr4/imports/JPQL_lexer.g4 b/core/parser/src/main/antlr4/imports/JPQL_lexer.g4 index e59047964f..c94673a8bf 100644 --- a/core/parser/src/main/antlr4/imports/JPQL_lexer.g4 +++ b/core/parser/src/main/antlr4/imports/JPQL_lexer.g4 @@ -125,6 +125,10 @@ MEMBER: [Mm][Ee][Mm][Bb][Ee][Rr]; OF: [Oo][Ff]; +TREAT: [Tt] [Rr] [Ee] [Aa] [Tt]; + +AS: [Aa] [Ss]; + Outer_function : [Oo][Uu][Tt][Ee][Rr]; Star_operator : '*'; diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/SimpleQueryGenerator.java b/core/parser/src/main/java/com/blazebit/persistence/impl/SimpleQueryGenerator.java index 18e7d5b3f9..b12cad2f3b 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/SimpleQueryGenerator.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/SimpleQueryGenerator.java @@ -35,7 +35,6 @@ import com.blazebit.persistence.impl.predicate.QuantifiableBinaryExpressionPredicate; import com.blazebit.persistence.impl.predicate.PredicateQuantifier; -import java.util.EnumSet; import java.util.List; /** @@ -499,6 +498,15 @@ public void visit(ArrayExpression expression) { sb.append(']'); } + @Override + public void visit(TreatExpression expression) { + sb.append("TREAT("); + expression.getExpression().accept(this); + sb.append(" AS "); + sb.append(expression.getType()); + sb.append(')'); + } + @Override public void visit(ArithmeticExpression expression) { ArithmeticOperator op = expression.getOp(); diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/AbortableVisitorAdapter.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/AbortableVisitorAdapter.java index 86de9c07ee..d7f588b495 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/AbortableVisitorAdapter.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/AbortableVisitorAdapter.java @@ -61,6 +61,11 @@ public Boolean visit(ArrayExpression expression) { return expression.getIndex().accept(this); } + @Override + public Boolean visit(TreatExpression expression) { + return expression.getExpression().accept(this); + } + @Override public Boolean visit(PropertyExpression expression) { return false; diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/AbstractCachingExpressionFactory.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/AbstractCachingExpressionFactory.java index 8ad7e19c6a..e8d3d7e715 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/AbstractCachingExpressionFactory.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/AbstractCachingExpressionFactory.java @@ -51,6 +51,18 @@ public PathExpression get() { }); } + @Override + public Expression createJoinPathExpression(final String expression) { + return getOrDefault("com.blazebit.persistence.parser.expression.cache.JoinPathExpression", expression, new Supplier() { + + @Override + public Expression get() { + return delegate.createJoinPathExpression(expression); + } + + }); + } + @Override public Expression createSimpleExpression(final String expression) { return getOrDefault("com.blazebit.persistence.parser.expression.cache.SimpleExpression", expression, new Supplier() { diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/AbstractExpressionFactory.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/AbstractExpressionFactory.java index cf3e216c04..fbab9a7cb9 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/AbstractExpressionFactory.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/AbstractExpressionFactory.java @@ -44,17 +44,19 @@ public abstract class AbstractExpressionFactory implements ExpressionFactory { protected static final Logger LOG = Logger.getLogger("com.blazebit.persistence.parser"); + private final boolean allowTreatJoinExtension; private final Set aggregateFunctions; - protected AbstractExpressionFactory(Set aggregateFunctions) { + protected AbstractExpressionFactory(Set aggregateFunctions, boolean allowTreatJoinExtension) { this.aggregateFunctions = aggregateFunctions; + this.allowTreatJoinExtension = allowTreatJoinExtension; } private Expression createExpression(RuleInvoker ruleInvoker, String expression) { - return createExpression(ruleInvoker, expression, true, true); + return createExpression(ruleInvoker, expression, true, true, false); } - private Expression createExpression(RuleInvoker ruleInvoker, String expression, boolean allowCaseWhen, boolean allowQuantifiedPredicates) { + private Expression createExpression(RuleInvoker ruleInvoker, String expression, boolean allowCaseWhen, boolean allowQuantifiedPredicates, boolean allowTreatJoinExtension) { if (expression == null) { throw new NullPointerException("expression"); } @@ -64,7 +66,7 @@ private Expression createExpression(RuleInvoker ruleInvoker, String expression, JPQLSelectExpressionLexer l = new JPQLSelectExpressionLexer(new ANTLRInputStream(expression)); configureLexer(l); CommonTokenStream tokens = new CommonTokenStream(l); - JPQLSelectExpressionParser p = new JPQLSelectExpressionParser(tokens, allowCaseWhen, allowQuantifiedPredicates); + JPQLSelectExpressionParser p = new JPQLSelectExpressionParser(tokens, allowCaseWhen, allowQuantifiedPredicates, allowTreatJoinExtension); configureParser(p); ParserRuleContext ctx = ruleInvoker.invokeRule(p); @@ -90,6 +92,17 @@ public ParserRuleContext invokeRule(JPQLSelectExpressionParser parser) { return (PathExpression) comp.getExpressions().get(0); } + @Override + public Expression createJoinPathExpression(String expression) { + return createExpression(new RuleInvoker() { + + @Override + public ParserRuleContext invokeRule(JPQLSelectExpressionParser parser) { + return parser.parseJoinPath(); + } + }, expression, false, false, allowTreatJoinExtension); + } + @Override public Expression createOrderByExpression(String expression) { return createExpression(new RuleInvoker() { @@ -98,7 +111,7 @@ public Expression createOrderByExpression(String expression) { public ParserRuleContext invokeRule(JPQLSelectExpressionParser parser) { return parser.parseOrderByClause(); } - }, expression, false, true); + }, expression, false, true, false); } @Override @@ -125,7 +138,7 @@ public Expression createScalarExpression(String expression) { public ParserRuleContext invokeRule(JPQLSelectExpressionParser parser) { return parser.parseScalarExpression(); } - }, expression, false, true); + }, expression, false, true, false); } @Override @@ -136,7 +149,7 @@ public Expression createArithmeticExpression(String expression) { public ParserRuleContext invokeRule(JPQLSelectExpressionParser parser) { return parser.parseArithmeticExpression(); } - }, expression, false, true); + }, expression, false, true, false); } @Override @@ -147,7 +160,7 @@ public Expression createStringExpression(String expression) { public ParserRuleContext invokeRule(JPQLSelectExpressionParser parser) { return parser.parseStringExpression(); } - }, expression, false, true); + }, expression, false, true, false); } @Override @@ -191,7 +204,7 @@ public Predicate createPredicateExpression(String expression, boolean allowQuant public ParserRuleContext invokeRule(JPQLSelectExpressionParser parser) { return parser.parsePredicateExpression(); } - }, expression, true, allowQuantifiedPredicates); + }, expression, true, allowQuantifiedPredicates, false); } private Expression createInItemExpression(String expression) { @@ -201,7 +214,7 @@ private Expression createInItemExpression(String expression) { public ParserRuleContext invokeRule(JPQLSelectExpressionParser parser) { return parser.parseInItemExpression(); } - }, expression, false, true); + }, expression, false, true, false); } protected void configureLexer(JPQLSelectExpressionLexer lexer) { diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/Expression.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/Expression.java index 5db0d27456..d8629a6084 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/Expression.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/Expression.java @@ -50,6 +50,8 @@ public static interface Visitor { public void visit(ArrayExpression expression); + public void visit(TreatExpression expression); + public void visit(CompositeExpression expression); public void visit(LiteralExpression expression); @@ -118,6 +120,8 @@ public static interface ResultVisitor { public T visit(ArrayExpression expression); + public T visit(TreatExpression expression); + public T visit(CompositeExpression expression); public T visit(LiteralExpression expression); diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/ExpressionFactory.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/ExpressionFactory.java index ab2fcb8e75..d2222e7aeb 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/ExpressionFactory.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/ExpressionFactory.java @@ -27,6 +27,8 @@ public interface ExpressionFactory { public PathExpression createPathExpression(String expression); + public Expression createJoinPathExpression(String expression); + public Expression createSimpleExpression(String expression); public Expression createCaseOperandExpression(String caseOperandExpression); diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/ExpressionFactoryImpl.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/ExpressionFactoryImpl.java index 3bd19edb8c..2579dec197 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/ExpressionFactoryImpl.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/ExpressionFactoryImpl.java @@ -1,49 +1,49 @@ -/* - * Copyright 2014 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.impl.expression; - -import java.util.Set; - -import com.blazebit.persistence.parser.JPQLSelectExpressionParser; - -import org.antlr.v4.runtime.ParserRuleContext; - -/** - * - * @author Christian Beikov - * @author Moritz Becker - * @since 1.0 - */ -public final class ExpressionFactoryImpl extends AbstractExpressionFactory { - - private final RuleInvoker simpleExpressionRuleInvoker = new RuleInvoker() { - - @Override - public ParserRuleContext invokeRule(JPQLSelectExpressionParser parser) { - return parser.parseSimpleExpression(); - } - }; - - public ExpressionFactoryImpl(Set aggregateFunctions) { - super(aggregateFunctions); - } - - @Override - protected RuleInvoker getSimpleExpressionRuleInvoker() { - return simpleExpressionRuleInvoker; - } - -} +/* + * Copyright 2014 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.impl.expression; + +import java.util.Set; + +import com.blazebit.persistence.parser.JPQLSelectExpressionParser; + +import org.antlr.v4.runtime.ParserRuleContext; + +/** + * + * @author Christian Beikov + * @author Moritz Becker + * @since 1.0 + */ +public final class ExpressionFactoryImpl extends AbstractExpressionFactory { + + private final RuleInvoker simpleExpressionRuleInvoker = new RuleInvoker() { + + @Override + public ParserRuleContext invokeRule(JPQLSelectExpressionParser parser) { + return parser.parseSimpleExpression(); + } + }; + + public ExpressionFactoryImpl(Set aggregateFunctions, boolean allowTreatJoinExtension) { + super(aggregateFunctions, allowTreatJoinExtension); + } + + @Override + protected RuleInvoker getSimpleExpressionRuleInvoker() { + return simpleExpressionRuleInvoker; + } + +} diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/JPQLSelectExpressionVisitorImpl.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/JPQLSelectExpressionVisitorImpl.java index 12797e1de0..928fa9e9ac 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/JPQLSelectExpressionVisitorImpl.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/JPQLSelectExpressionVisitorImpl.java @@ -164,7 +164,7 @@ private String getLiteralString(Expression expr) { } @Override - public Expression visitGeneral_subpath(JPQLSelectExpressionParser.General_subpathContext ctx) { + public Expression visitSimple_subpath(JPQLSelectExpressionParser.Simple_subpathContext ctx) { List pathElements = new ArrayList(); pathElements.add((PathElementExpression) ctx.general_path_start().accept(this)); for (JPQLSelectExpressionParser.General_path_elementContext generalPathElem : ctx.general_path_element()) { @@ -173,9 +173,31 @@ public Expression visitGeneral_subpath(JPQLSelectExpressionParser.General_subpat return new PathExpression(pathElements); } + @Override + public Expression visitTreated_subpath(Treated_subpathContext ctx) { + TreatExpression treatExpression = new TreatExpression(ctx.general_subpath().accept(this), ctx.subtype().getText()); + List followingPaths = ctx.general_path_element(); + Expression finalExpression = treatExpression; + + if (followingPaths.size() > 0) { + List pathProperties = new ArrayList(followingPaths.size() + 1); + PathExpression path = new PathExpression(pathProperties); + pathProperties.add(treatExpression); + + for (int i = 0; i < followingPaths.size(); i++) { + // TODO: Can here be arrays or is it just path elements? + pathProperties.add((PathElementExpression) followingPaths.get(i).accept(this)); + } + + finalExpression = path; + } + + return finalExpression; + } + @Override public Expression visitPath(JPQLSelectExpressionParser.PathContext ctx) { - PathExpression result = (PathExpression) ctx.general_subpath().accept(this); + PathExpression result = wrapPath(ctx.general_subpath().accept(this)); result.getExpressions().add((PathElementExpression) ctx.general_path_element().accept(this)); return result; } @@ -223,6 +245,50 @@ public Expression visitKey_value_expression(JPQLSelectExpressionParser.Key_value return new FunctionExpression(ctx.name.getText(), Arrays.asList(collectionPath)); } + @Override + public Expression visitTreated_key_value_expression(Treated_key_value_expressionContext ctx) { + return new TreatExpression(ctx.key_value_expression().accept(this), ctx.subtype().getText()); + } + + @Override + public Expression visitSimpleJoinPathExpression(SimpleJoinPathExpressionContext ctx) { + PathExpression path = (PathExpression) ctx.simple_subpath().accept(this); + path.getExpressions().add((PathElementExpression) ctx.general_path_element().accept(this)); + return path; + } + + @Override + public Expression visitExtendedJoinPathExpression(ExtendedJoinPathExpressionContext ctx) { + PathExpression path = wrapPath(ctx.treated_subpath().accept(this)); + path.getExpressions().add((PathElementExpression) ctx.general_path_element().accept(this)); + return path; + } + + @Override + public Expression visitSingleJoinElementExpression(SingleJoinElementExpressionContext ctx) { + return ctx.single_element_path_expression().accept(this); + } + + @Override + public Expression visitTreatJoinPathExpression(TreatJoinPathExpressionContext ctx) { + return new TreatExpression(ctx.join_path_expression().accept(this), ctx.subtype().getText()); + } + + @Override + public Expression visitSimplePath(SimplePathContext ctx) { + PathExpression path = (PathExpression) ctx.simple_subpath().accept(this); + path.getExpressions().add((PathElementExpression) ctx.general_path_element().accept(this)); + return path; + } + + @Override + public Expression visitTreatedRootPath(TreatedRootPathContext ctx) { + TreatExpression treatExpression = new TreatExpression(wrapPath(new PropertyExpression(ctx.identifier().getText())), ctx.subtype().getText()); + PathExpression path = (PathExpression) ctx.simple_subpath().accept(this); + path.getExpressions().add(0, treatExpression); + return path; + } + @Override public Expression visitIndexFunction(IndexFunctionContext ctx) { PathExpression collectionPath = (PathExpression) ctx.collection_valued_path_expression().accept(this); @@ -479,6 +545,11 @@ public Expression visitParseSimpleExpression(JPQLSelectExpressionParser.ParseSim return unwrap(super.visitParseSimpleExpression(ctx)); } + @Override + public Expression visitParseJoinPath(JPQLSelectExpressionParser.ParseJoinPathContext ctx) { + return unwrap(super.visitParseJoinPath(ctx)); + } + @Override public Expression visitParseSimpleSubqueryExpression(JPQLSelectExpressionParser.ParseSimpleSubqueryExpressionContext ctx) { return unwrap(super.visitParseSimpleSubqueryExpression(ctx)); @@ -688,6 +759,16 @@ private StringBuilder tokenListToString(List tokens) { return sb; } + private PathExpression wrapPath(Expression expression) { + if (expression instanceof PathExpression) { + return (PathExpression) expression; + } + + PathExpression p = new PathExpression(); + p.getExpressions().add((PathElementExpression) expression); + return p; + } + private Expression unwrap(Expression expr) { if (expr instanceof CompositeExpression) { CompositeExpression composite = (CompositeExpression) expr; diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/PathExpression.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/PathExpression.java index 5ca2782065..7f006baeba 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/PathExpression.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/PathExpression.java @@ -1,159 +1,168 @@ -/* - * Copyright 2014 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.impl.expression; - -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author Christian Beikov - * @author Moritz Becker - * @since 1.0 - */ -public class PathExpression extends AbstractExpression implements Expression { - - private final List pathProperties; - private PathReference pathReference; - private boolean usedInCollectionFunction = false; - private boolean collectionKeyPath; - - public PathExpression() { - this(new ArrayList(), false); - } - - public PathExpression(List pathProperties) { - this(pathProperties, false); - } - - public PathExpression(List pathProperties, boolean isCollectionKeyPath) { - this.pathProperties = pathProperties; - this.collectionKeyPath = isCollectionKeyPath; - } - - public PathExpression(List pathProperties, PathReference pathReference, boolean usedInCollectionFunction, boolean collectionKeyPath) { - this.pathProperties = pathProperties; - this.pathReference = pathReference; - this.usedInCollectionFunction = usedInCollectionFunction; - this.collectionKeyPath = collectionKeyPath; - } - - @Override - public Expression clone() { - int size = pathProperties.size(); - List newPathProperties = new ArrayList(size); - - for (int i = 0; i < size; i++) { - newPathProperties.add(pathProperties.get(i).clone()); - } - - return new PathExpression(newPathProperties, pathReference, usedInCollectionFunction, collectionKeyPath); - } - - @Override - public void accept(Visitor visitor) { - visitor.visit(this); - } - - @Override - public T accept(ResultVisitor visitor) { - return visitor.visit(this); - } - - public List getExpressions() { - return pathProperties; - } - - public PathReference getPathReference() { - return pathReference; - } - - public void setPathReference(PathReference pathReference) { - this.pathReference = pathReference; - } - - public Object getBaseNode() { - if (pathReference == null) { - return null; - } - - return pathReference.getBaseNode(); - } - - public String getField() { - if (pathReference == null) { - return null; - } - - return pathReference.getField(); - } - - public boolean isUsedInCollectionFunction() { - return usedInCollectionFunction; - } - - public void setUsedInCollectionFunction(boolean collectionValued) { - this.usedInCollectionFunction = collectionValued; - } - - public String getPath() { - return toString(); - } - - public boolean isCollectionKeyPath() { - return collectionKeyPath; - } - - public void setCollectionKeyPath(boolean collectionKeyPath) { - this.collectionKeyPath = collectionKeyPath; - } - - /* - * The following equals and hashCode implementation makes it possible that expressions which have different path properties but - * reference the same object, are equal. - */ - @Override - public int hashCode() { - int hash = 3; - if (this.pathReference != null) { - hash = 31 * hash + (this.pathReference != null ? this.pathReference.hashCode() : 0); - } else { - hash = 31 * hash + (this.pathProperties != null ? this.pathProperties.hashCode() : 0); - } - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final PathExpression other = (PathExpression) obj; - if (this.pathReference != null|| other.pathReference != null) { - if (this.pathReference != other.pathReference && (this.pathReference == null || !this.pathReference.equals(other.pathReference))) { - return false; - } - } else { - if (this.pathProperties != other.pathProperties && (this.pathProperties == null || !this.pathProperties.equals(other.pathProperties))) { - return false; - } - } - return true; - } -} +/* + * Copyright 2014 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.impl.expression; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Christian Beikov + * @author Moritz Becker + * @since 1.0 + */ +public class PathExpression extends AbstractExpression implements Expression { + + private final List pathProperties; + private PathReference pathReference; + private boolean usedInCollectionFunction = false; + private boolean collectionKeyPath; + private boolean hasTreatedSubpath; + + public PathExpression() { + this(new ArrayList(), false); + } + + public PathExpression(List pathProperties) { + this(pathProperties, false); + } + + public PathExpression(List pathProperties, boolean isCollectionKeyPath) { + this.pathProperties = pathProperties; + this.collectionKeyPath = isCollectionKeyPath; + } + + public PathExpression(List pathProperties, PathReference pathReference, boolean usedInCollectionFunction, boolean collectionKeyPath) { + this.pathProperties = pathProperties; + this.pathReference = pathReference; + this.usedInCollectionFunction = usedInCollectionFunction; + this.collectionKeyPath = collectionKeyPath; + } + + @Override + public PathExpression clone() { + int size = pathProperties.size(); + List newPathProperties = new ArrayList(size); + + for (int i = 0; i < size; i++) { + newPathProperties.add(pathProperties.get(i).clone()); + } + + return new PathExpression(newPathProperties, pathReference, usedInCollectionFunction, collectionKeyPath); + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public T accept(ResultVisitor visitor) { + return visitor.visit(this); + } + + public List getExpressions() { + return pathProperties; + } + + public PathReference getPathReference() { + return pathReference; + } + + public void setPathReference(PathReference pathReference) { + this.pathReference = pathReference; + } + + public Object getBaseNode() { + if (pathReference == null) { + return null; + } + + return pathReference.getBaseNode(); + } + + public String getField() { + if (pathReference == null) { + return null; + } + + return pathReference.getField(); + } + + public boolean isUsedInCollectionFunction() { + return usedInCollectionFunction; + } + + public void setUsedInCollectionFunction(boolean collectionValued) { + this.usedInCollectionFunction = collectionValued; + } + + public String getPath() { + return toString(); + } + + public boolean isCollectionKeyPath() { + return collectionKeyPath; + } + + public void setCollectionKeyPath(boolean collectionKeyPath) { + this.collectionKeyPath = collectionKeyPath; + } + + public boolean hasTreatedSubpath() { + return hasTreatedSubpath; + } + + public void setHasTreatedSubpath(boolean hasTreatedSubpath) { + this.hasTreatedSubpath = hasTreatedSubpath; + } + + /* + * The following equals and hashCode implementation makes it possible that expressions which have different path properties but + * reference the same object, are equal. + */ + @Override + public int hashCode() { + int hash = 3; + if (this.pathReference != null) { + hash = 31 * hash + (this.pathReference != null ? this.pathReference.hashCode() : 0); + } else { + hash = 31 * hash + (this.pathProperties != null ? this.pathProperties.hashCode() : 0); + } + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PathExpression other = (PathExpression) obj; + if (this.pathReference != null|| other.pathReference != null) { + if (this.pathReference != other.pathReference && (this.pathReference == null || !this.pathReference.equals(other.pathReference))) { + return false; + } + } else { + if (this.pathProperties != other.pathProperties && (this.pathProperties == null || !this.pathProperties.equals(other.pathProperties))) { + return false; + } + } + return true; + } +} diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/PathReference.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/PathReference.java index feffb070b9..79e426a602 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/PathReference.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/PathReference.java @@ -1,16 +1,19 @@ -package com.blazebit.persistence.impl.expression; - -/** - * TODO: documentation - * - * @author Christian Beikov - * @since 1.1.0 - * - */ -public interface PathReference { - - // Although this node will always be a JoinNode we will use casting at use site to be able to reuse the parser - public Object getBaseNode(); - - public String getField(); -} +package com.blazebit.persistence.impl.expression; + +/** + * TODO: documentation + * + * @author Christian Beikov + * @since 1.1.0 + * + */ +public interface PathReference { + + // Although this node will always be a JoinNode we will use casting at use site to be able to reuse the parser + public Object getBaseNode(); + + public String getField(); + + public String getTreatTypeName(); + +} diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/PredicateModifyingResultVisitorAdapter.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/PredicateModifyingResultVisitorAdapter.java index 2659be8f8f..44d706dca0 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/PredicateModifyingResultVisitorAdapter.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/PredicateModifyingResultVisitorAdapter.java @@ -60,6 +60,12 @@ public Expression visit(ArrayExpression expression) { return expression; } + @Override + public Expression visit(TreatExpression expression) { + expression.getExpression().accept(this); + return expression; + } + @Override public Expression visit(PropertyExpression expression) { return expression; diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/SimplePathReference.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/SimplePathReference.java index fdf2fa4a0a..0d74fdeffd 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/SimplePathReference.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/SimplePathReference.java @@ -1,55 +1,68 @@ -package com.blazebit.persistence.impl.expression; - - -public class SimplePathReference implements PathReference { - - private final Object baseNode; - private final String field; - - public SimplePathReference(Object baseNode, String field) { - this.baseNode = baseNode; - this.field = field; - } - - @Override - public Object getBaseNode() { - return baseNode; - } - - @Override - public String getField() { - return field; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((baseNode == null) ? 0 : baseNode.hashCode()); - result = prime * result + ((field == null) ? 0 : field.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof PathReference)) - return false; - PathReference other = (PathReference) obj; - if (baseNode == null) { - if (other.getBaseNode() != null) - return false; - } else if (!baseNode.equals(other.getBaseNode())) - return false; - if (field == null) { - if (other.getField() != null) - return false; - } else if (!field.equals(other.getField())) - return false; - return true; - } - -} +package com.blazebit.persistence.impl.expression; + + +public class SimplePathReference implements PathReference { + + private final Object baseNode; + private final String field; + private final String typeName; + + public SimplePathReference(Object baseNode, String field, String typeName) { + this.baseNode = baseNode; + this.field = field; + this.typeName = typeName; + } + + @Override + public Object getBaseNode() { + return baseNode; + } + + @Override + public String getField() { + return field; + } + + @Override + public String getTreatTypeName() { + return typeName; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((baseNode == null) ? 0 : baseNode.hashCode()); + result = prime * result + ((field == null) ? 0 : field.hashCode()); + result = prime * result + ((typeName == null) ? 0 : typeName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof PathReference)) + return false; + PathReference other = (PathReference) obj; + if (baseNode == null) { + if (other.getBaseNode() != null) + return false; + } else if (!baseNode.equals(other.getBaseNode())) + return false; + if (field == null) { + if (other.getField() != null) + return false; + } else if (!field.equals(other.getField())) + return false; + if (typeName == null) { + if (other.getTreatTypeName() != null) + return false; + } else if (!typeName.equals(other.getTreatTypeName())) + return false; + return true; + } + +} diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/SubqueryExpressionFactory.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/SubqueryExpressionFactory.java index 6a831c6ef9..3c89a5e8a7 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/SubqueryExpressionFactory.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/SubqueryExpressionFactory.java @@ -1,94 +1,99 @@ -/* - * Copyright 2014 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.impl.expression; - -import java.util.Set; - -import com.blazebit.persistence.parser.JPQLSelectExpressionParser; - -import org.antlr.v4.runtime.ParserRuleContext; - -/** - * - * @author Christian Beikov - * @author Moritz Becker - * @since 1.0 - */ -public class SubqueryExpressionFactory extends AbstractExpressionFactory { - - private final ExpressionFactory delegate; - - private final RuleInvoker simpleExpressionRuleInvoker = new RuleInvoker() { - - @Override - public ParserRuleContext invokeRule(JPQLSelectExpressionParser parser) { - return parser.parseSimpleSubqueryExpression(); - } - }; - - public SubqueryExpressionFactory(Set aggregateFunctions, ExpressionFactory delegate) { - super(aggregateFunctions); - this.delegate = delegate; - } - - @Override - public Expression createSimpleExpression(String expression) { - return super.createSimpleExpression(expression); - } - - @Override - protected RuleInvoker getSimpleExpressionRuleInvoker() { - return simpleExpressionRuleInvoker; - } - - // Delegates - - @Override - public PathExpression createPathExpression(String expression) { - return delegate.createPathExpression(expression); - } - - @Override - public Expression createCaseOperandExpression(String caseOperandExpression) { - return delegate.createCaseOperandExpression(caseOperandExpression); - } - - @Override - public Expression createScalarExpression(String expression) { - return delegate.createScalarExpression(expression); - } - - @Override - public Expression createArithmeticExpression(String expression) { - return delegate.createArithmeticExpression(expression); - } - - @Override - public Expression createStringExpression(String expression) { - return delegate.createStringExpression(expression); - } - - @Override - public Expression createOrderByExpression(String expression) { - return delegate.createOrderByExpression(expression); - } - - @Override - public Expression createInPredicateExpression(String[] parameterOrLiteralExpressions) { - return delegate.createInPredicateExpression(parameterOrLiteralExpressions); - } - -} +/* + * Copyright 2014 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.impl.expression; + +import java.util.Set; + +import com.blazebit.persistence.parser.JPQLSelectExpressionParser; + +import org.antlr.v4.runtime.ParserRuleContext; + +/** + * + * @author Christian Beikov + * @author Moritz Becker + * @since 1.0 + */ +public class SubqueryExpressionFactory extends AbstractExpressionFactory { + + private final ExpressionFactory delegate; + + private final RuleInvoker simpleExpressionRuleInvoker = new RuleInvoker() { + + @Override + public ParserRuleContext invokeRule(JPQLSelectExpressionParser parser) { + return parser.parseSimpleSubqueryExpression(); + } + }; + + public SubqueryExpressionFactory(Set aggregateFunctions, boolean allowTreatJoinExtension, ExpressionFactory delegate) { + super(aggregateFunctions, allowTreatJoinExtension); + this.delegate = delegate; + } + + @Override + public Expression createSimpleExpression(String expression) { + return super.createSimpleExpression(expression); + } + + @Override + protected RuleInvoker getSimpleExpressionRuleInvoker() { + return simpleExpressionRuleInvoker; + } + + // Delegates + + @Override + public PathExpression createPathExpression(String expression) { + return delegate.createPathExpression(expression); + } + + @Override + public Expression createJoinPathExpression(String expression) { + return delegate.createJoinPathExpression(expression); + } + + @Override + public Expression createCaseOperandExpression(String caseOperandExpression) { + return delegate.createCaseOperandExpression(caseOperandExpression); + } + + @Override + public Expression createScalarExpression(String expression) { + return delegate.createScalarExpression(expression); + } + + @Override + public Expression createArithmeticExpression(String expression) { + return delegate.createArithmeticExpression(expression); + } + + @Override + public Expression createStringExpression(String expression) { + return delegate.createStringExpression(expression); + } + + @Override + public Expression createOrderByExpression(String expression) { + return delegate.createOrderByExpression(expression); + } + + @Override + public Expression createInPredicateExpression(String[] parameterOrLiteralExpressions) { + return delegate.createInPredicateExpression(parameterOrLiteralExpressions); + } + +} diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/TreatExpression.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/TreatExpression.java new file mode 100644 index 0000000000..96131a70b4 --- /dev/null +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/TreatExpression.java @@ -0,0 +1,74 @@ +/* + * Copyright 2014 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.impl.expression; + +/** + * + * @author Christian Beikov + * @since 1.2 + */ +public class TreatExpression extends AbstractExpression implements PathElementExpression { + + private final Expression expression; + private final String type; + + public TreatExpression(Expression expression, String type) { + this.expression = expression; + this.type = type; + } + + @Override + public TreatExpression clone() { + return new TreatExpression(expression.clone(), type); + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public T accept(ResultVisitor visitor) { + return visitor.visit(this); + } + + public Expression getExpression() { + return expression; + } + + public String getType() { + return type; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TreatExpression)) return false; + + TreatExpression that = (TreatExpression) o; + + if (expression != null ? !expression.equals(that.expression) : that.expression != null) return false; + return type != null ? type.equals(that.type) : that.type == null; + + } + + @Override + public int hashCode() { + int result = expression != null ? expression.hashCode() : 0; + result = 31 * result + (type != null ? type.hashCode() : 0); + return result; + } +} diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/VisitorAdapter.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/VisitorAdapter.java index 2f801956ef..668d325571 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/VisitorAdapter.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/VisitorAdapter.java @@ -56,6 +56,11 @@ public void visit(ArrayExpression expression) { expression.getIndex().accept(this); } + @Override + public void visit(TreatExpression expression) { + expression.getExpression().accept(this); + } + @Override public void visit(PropertyExpression expression) { } diff --git a/core/parser/src/test/java/com/blazebit/persistence/impl/expression/AbstractParserTest.java b/core/parser/src/test/java/com/blazebit/persistence/impl/expression/AbstractParserTest.java index 8812db4002..e9c38c220a 100644 --- a/core/parser/src/test/java/com/blazebit/persistence/impl/expression/AbstractParserTest.java +++ b/core/parser/src/test/java/com/blazebit/persistence/impl/expression/AbstractParserTest.java @@ -118,6 +118,10 @@ protected Expression parse(String expr) { return ef.createSimpleExpression(expr); } + protected Expression parseJoin(String expr) { + return ef.createJoinPathExpression(expr); + } + protected Predicate parsePredicate(String expr, boolean allowQuantifiedPredicates) { return ef.createPredicateExpression(expr, allowQuantifiedPredicates); } @@ -166,6 +170,23 @@ protected PathExpression path(String... properties) { return p; } + protected PathExpression path(Object... pathElements) { + PathExpression p = new PathExpression(new ArrayList()); + for (Object pathElem : pathElements) { + if (pathElem instanceof String) { + String property = (String) pathElem; + if (property.contains("[")) { + p.getExpressions().add(array(property)); + } else { + p.getExpressions().add(new PropertyExpression(property)); + } + } else { + p.getExpressions().add((PathElementExpression) pathElem); + } + } + return p; + } + protected ArrayExpression array(String expr) { int firstIndex = expr.indexOf('['); int lastIndex = expr.indexOf(']'); @@ -182,6 +203,10 @@ protected ArrayExpression array(String expr) { return new ArrayExpression(new PropertyExpression(base), indexExpr); } + protected TreatExpression treat(PathExpression path, String type) { + return new TreatExpression(path, type); + } + protected ParameterExpression parameter(String name) { return new ParameterExpression(name); } diff --git a/core/parser/src/test/java/com/blazebit/persistence/impl/expression/AbstractTestExpressionFactory.java b/core/parser/src/test/java/com/blazebit/persistence/impl/expression/AbstractTestExpressionFactory.java index c5c3d84ff7..01f805a70f 100644 --- a/core/parser/src/test/java/com/blazebit/persistence/impl/expression/AbstractTestExpressionFactory.java +++ b/core/parser/src/test/java/com/blazebit/persistence/impl/expression/AbstractTestExpressionFactory.java @@ -1,54 +1,54 @@ -/* - * Copyright 2014 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.impl.expression; - -import java.util.Set; -import java.util.logging.Level; - -import org.antlr.v4.runtime.atn.PredictionMode; - -import com.blazebit.persistence.parser.JPQLSelectExpressionLexer; -import com.blazebit.persistence.parser.JPQLSelectExpressionParser; - -/** - * - * @author Moritz Becker - */ -public abstract class AbstractTestExpressionFactory extends AbstractExpressionFactory { - - - public AbstractTestExpressionFactory(Set aggregateFunctions) { - super(aggregateFunctions); - } - - @Override - protected void configureLexer(JPQLSelectExpressionLexer lexer) { - lexer.removeErrorListeners(); - lexer.addErrorListener(ERR_LISTENER); - } - - @Override - protected void configureParser(JPQLSelectExpressionParser parser) { - if (LOG.isLoggable(Level.FINEST)) { - parser.setTrace(true); - } - - parser.removeErrorListeners(); - parser.addErrorListener(ERR_LISTENER); - parser.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION); - } - -} +/* + * Copyright 2014 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.impl.expression; + +import java.util.Set; +import java.util.logging.Level; + +import org.antlr.v4.runtime.atn.PredictionMode; + +import com.blazebit.persistence.parser.JPQLSelectExpressionLexer; +import com.blazebit.persistence.parser.JPQLSelectExpressionParser; + +/** + * + * @author Moritz Becker + */ +public abstract class AbstractTestExpressionFactory extends AbstractExpressionFactory { + + + public AbstractTestExpressionFactory(Set aggregateFunctions) { + super(aggregateFunctions, true); + } + + @Override + protected void configureLexer(JPQLSelectExpressionLexer lexer) { + lexer.removeErrorListeners(); + lexer.addErrorListener(ERR_LISTENER); + } + + @Override + protected void configureParser(JPQLSelectExpressionParser parser) { + if (LOG.isLoggable(Level.FINEST)) { + parser.setTrace(true); + } + + parser.removeErrorListeners(); + parser.addErrorListener(ERR_LISTENER); + parser.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION); + } + +} diff --git a/core/parser/src/test/java/com/blazebit/persistence/impl/expression/GeneralParserTest.java b/core/parser/src/test/java/com/blazebit/persistence/impl/expression/GeneralParserTest.java index ee088df39e..c725dece89 100644 --- a/core/parser/src/test/java/com/blazebit/persistence/impl/expression/GeneralParserTest.java +++ b/core/parser/src/test/java/com/blazebit/persistence/impl/expression/GeneralParserTest.java @@ -207,6 +207,7 @@ public void testArrayIndexPath() { assertEquals(path("versions[test.x.y]"), result); } + // TODO: #210 @Ignore("#210") @Test public void testArrayIndexArithmetic() { @@ -219,6 +220,7 @@ public void testArrayIndexArithmetic() { assertEquals(expected, result); } + // TODO: #210 @Ignore("#210") @Test public void testArrayIndexArithmeticMixed() { @@ -231,6 +233,7 @@ public void testArrayIndexArithmeticMixed() { assertEquals(expected, result); } + // TODO: #210 @Ignore("#210") @Test public void testArrayIndexArithmeticLiteral() { @@ -818,4 +821,136 @@ public void testConditionalCaseWhen() { ); assertEquals(expected, result); } + + @Test + public void testTreatPredicate1() { + Predicate result = parsePredicate("TREAT(d AS GoodDocument).name IS NOT NULL", false); + + Predicate expected = new IsNullPredicate(path(treat(path("d"), "GoodDocument"), "name"), true); + assertEquals(expected, result); + } + + @Test + public void testTreatPredicate2() { + Predicate result = parsePredicate(":param = TREAT(d AS GoodDocument).name", false); + + Predicate expected = new EqPredicate(parameter("param"), path(treat(path("d"), "GoodDocument"), "name")); + assertEquals(expected, result); + } + + @Test(expected = SyntaxErrorException.class) + public void testInvalidTreatRootPathOnly() { + // Can't have treat node as expression root + parse("TREAT(d AS GoodDocument)"); + } + + @Test + public void testTreatRootPathDerference() { + Expression result = parse("TREAT(d AS GoodDocument).goodName"); + + Expression expected = path(treat(path("d"), "GoodDocument"), "goodName"); + assertEquals(expected, result); + } + + @Test + public void testTreatRootPathMultipleDerference() { + Expression result = parse("TREAT(d AS GoodDocument).embeddable.goodName"); + + Expression expected = path(treat(path("d"), "GoodDocument"), "embeddable", "goodName"); + assertEquals(expected, result); + } + + @Test(expected = SyntaxErrorException.class) + public void testInvalidTreatSubpathPathOnly() { + // Can't have treat node as expression root + parse("TREAT(d.people AS Employee)"); + } + + @Test + public void testTreatSubpathPathDerference() { + Expression result = parse("TREAT(d.people AS Employee).name"); + + Expression expected = path(treat(path("d", "people"), "Employee"), "name"); + assertEquals(expected, result); + } + + @Test + public void testTreatSubpathPathMultipleDerference() { + Expression result = parse("TREAT(d.people AS Employee).embeddable.name"); + + Expression expected = path(treat(path("d", "people"), "Employee"), "embeddable", "name"); + assertEquals(expected, result); + } + + @Test(expected = SyntaxErrorException.class) + public void testTreatDeepSubpathPathOnly() { + // Can't have treat node as expression root + parse("TREAT(d.embeddable.people AS Employee)"); + } + + @Test + public void testTreatDeepSubpathPathDerference() { + Expression result = parse("TREAT(d.embeddable.people AS Employee).name"); + + Expression expected = path(treat(path("d", "embeddable", "people"), "Employee"), "name"); + assertEquals(expected, result); + } + + @Test + public void testTreatDeepSubpathPathMultipleDerference() { + Expression result = parse("TREAT(d.embeddable.people AS Employee).embeddable.name"); + + Expression expected = path(treat(path("d", "embeddable", "people"), "Employee"), "embeddable", "name"); + assertEquals(expected, result); + } + + @Test + public void testTreatJoin() { + Expression result = parseJoin("TREAT(d.people AS Employee)"); + + Expression expected = treat(path("d", "people"), "Employee"); + assertEquals(expected, result); + } + + @Test + public void testTreatJoinDeep() { + Expression result = parseJoin("TREAT(d.embeddable.people AS Employee)"); + + Expression expected = treat(path("d", "embeddable", "people"), "Employee"); + assertEquals(expected, result); + } + + /* NOTE: this is not JPA standard */ + + @Test + public void testJoinTreated() { + Expression result = parseJoin("TREAT(d AS GoodDocument).people"); + + Expression expected = path(treat(path("d"), "GoodDocument"), "people"); + assertEquals(expected, result); + } + + @Test + public void testDeepJoinTreated() { + Expression result = parseJoin("TREAT(d AS GoodDocument).embeddable.people"); + + Expression expected = path(treat(path("d"), "GoodDocument"), "embeddable", "people"); + assertEquals(expected, result); + } + + @Test + public void testTreatJoinTreated() { + Expression result = parseJoin("TREAT(TREAT(d AS GoodDocument).people AS Employee)"); + + Expression expected = treat(path(treat(path("d"), "GoodDocument"), "people"), "Employee"); + assertEquals(expected, result); + } + + @Test + public void testDeepTreatJoinTreated() { + Expression result = parseJoin("TREAT(TREAT(d AS GoodDocument).embeddable.people AS Employee)"); + + Expression expected = treat(path(treat(path("d"), "GoodDocument"), "embeddable", "people"), "Employee"); + assertEquals(expected, result); + } } diff --git a/core/parser/src/test/java/com/blazebit/persistence/impl/expression/ParserPerformanceTest.java b/core/parser/src/test/java/com/blazebit/persistence/impl/expression/ParserPerformanceTest.java index 8635daf7b8..26bae6eb57 100644 --- a/core/parser/src/test/java/com/blazebit/persistence/impl/expression/ParserPerformanceTest.java +++ b/core/parser/src/test/java/com/blazebit/persistence/impl/expression/ParserPerformanceTest.java @@ -74,7 +74,7 @@ public void testLargeExpression() { private void doTest(String expression) { JPQLSelectExpressionLexer l = new JPQLSelectExpressionLexer(new ANTLRInputStream(expression)); CommonTokenStream tokens = new CommonTokenStream(l); - JPQLSelectExpressionParser p = new JPQLSelectExpressionParser(tokens, true, true); + JPQLSelectExpressionParser p = new JPQLSelectExpressionParser(tokens, true, true, true); p.getInterpreter().setPredictionMode(PredictionMode.SLL); p.setErrorHandler(new BailErrorStrategy()); diff --git a/core/parser/src/test/java/com/blazebit/persistence/impl/expression/SimpleCachingExpressionFactoryPerformanceTest.java b/core/parser/src/test/java/com/blazebit/persistence/impl/expression/SimpleCachingExpressionFactoryPerformanceTest.java index bc847e0721..a22fa81da1 100644 --- a/core/parser/src/test/java/com/blazebit/persistence/impl/expression/SimpleCachingExpressionFactoryPerformanceTest.java +++ b/core/parser/src/test/java/com/blazebit/persistence/impl/expression/SimpleCachingExpressionFactoryPerformanceTest.java @@ -40,8 +40,8 @@ public class SimpleCachingExpressionFactoryPerformanceTest { @Rule public TestRule benchmarkRun = new BenchmarkRule(); - private final ExpressionFactory cachingExpressionFactory = new SimpleCachingExpressionFactory(new ExpressionFactoryImpl(new HashSet())); - private final ExpressionFactory nonCachingExpressionFactory = new ExpressionFactoryImpl(new HashSet()); + private final ExpressionFactory cachingExpressionFactory = new SimpleCachingExpressionFactory(new ExpressionFactoryImpl(new HashSet(), true)); + private final ExpressionFactory nonCachingExpressionFactory = new ExpressionFactoryImpl(new HashSet(), true); @BeforeClass public static void beforeClass() { diff --git a/core/parser/src/test/java/com/blazebit/persistence/impl/expression/SimpleCachingExpressionFactoryTest.java b/core/parser/src/test/java/com/blazebit/persistence/impl/expression/SimpleCachingExpressionFactoryTest.java index ef90967198..599ebca09d 100644 --- a/core/parser/src/test/java/com/blazebit/persistence/impl/expression/SimpleCachingExpressionFactoryTest.java +++ b/core/parser/src/test/java/com/blazebit/persistence/impl/expression/SimpleCachingExpressionFactoryTest.java @@ -29,7 +29,7 @@ public class SimpleCachingExpressionFactoryTest { @Test public void testCreateSimpleExpressionCache() { - ExpressionFactory ef = new SimpleCachingExpressionFactory(new ExpressionFactoryImpl(new HashSet())); + ExpressionFactory ef = new SimpleCachingExpressionFactory(new ExpressionFactoryImpl(new HashSet(), true)); String expressionString = "SIZE(Hello.world[:hahaha].criteria[1].api.lsls[a.b.c.d.e]) + SIZE(Hello.world[:hahaha].criteria[1].api.lsls[a.b.c.d.e])"; Expression expr1 = ef.createSimpleExpression(expressionString); diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/AbstractCoreTest.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/AbstractCoreTest.java index 4f7644a2f0..a9148bd38f 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/AbstractCoreTest.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/AbstractCoreTest.java @@ -177,6 +177,36 @@ protected String countPaginated(String string, boolean distinct) { return sb.toString(); } + protected String treatRoot(String path, Class type, String property) { + if (jpaProvider.supportsRootTreat()) { + return "TREAT(" + path + " AS " + type.getSimpleName() + ")." + property; + } else if (jpaProvider.supportsSubtypePropertyResolving()) { + return path + "." + property; + } + + throw new IllegalArgumentException("Treat should not be used as the JPA provider does not support subtype property access!"); + } + + protected String treatJoin(String path, Class type) { + if (jpaProvider.supportsRootTreat()) { + return "TREAT(" + path + " AS " + type.getSimpleName() + ")"; + } else if (jpaProvider.supportsSubtypePropertyResolving()) { + return path; + } + + throw new IllegalArgumentException("Treat should not be used as the JPA provider does not support subtype property access!"); + } + + protected String treatJoin(String path, Class type, String property) { + if (jpaProvider.supportsRootTreat()) { + return "TREAT(" + path + " AS " + type.getSimpleName() + ")." + property; + } else if (jpaProvider.supportsSubtypePropertyResolving()) { + return path + "." + property; + } + + throw new IllegalArgumentException("Treat should not be used as the JPA provider does not support subtype property access!"); + } + protected String renderNullPrecedence(String expression, String order, String nulls) { return renderNullPrecedence(expression, expression, order, nulls); } diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/JuniorProjectLeader.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/JuniorProjectLeader.java index ee5549d6bf..5755e3cdb1 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/JuniorProjectLeader.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/JuniorProjectLeader.java @@ -23,7 +23,7 @@ * @author Christian Beikov * @since 1.0 */ -//@Entity +@Entity @DiscriminatorValue("J") public class JuniorProjectLeader extends ProjectLeader { diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/LargeProject.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/LargeProject.java index aa2faa801f..8208468890 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/LargeProject.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/LargeProject.java @@ -23,7 +23,7 @@ * @author Christian Beikov * @since 1.0 */ -//@Entity +@Entity @DiscriminatorValue("L") public class LargeProject extends Project { diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/PolymorphicSub1.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/PolymorphicSub1.java index f270c27140..1bcf107ec2 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/PolymorphicSub1.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/PolymorphicSub1.java @@ -29,6 +29,7 @@ public class PolymorphicSub1 extends PolymorphicBase { private static final long serialVersionUID = 1L; private IntIdEntity relation1; + private PolymorphicBase parent1; private Integer sub1Value; public PolymorphicSub1() { @@ -43,6 +44,15 @@ public void setRelation1(IntIdEntity relation1) { this.relation1 = relation1; } + @ManyToOne(fetch = FetchType.LAZY) + public PolymorphicBase getParent1() { + return parent1; + } + + public void setParent1(PolymorphicBase parent1) { + this.parent1 = parent1; + } + public Integer getSub1Value() { return sub1Value; } diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/PolymorphicSub2.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/PolymorphicSub2.java index 62c145d531..38524f5343 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/PolymorphicSub2.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/PolymorphicSub2.java @@ -29,6 +29,7 @@ public class PolymorphicSub2 extends PolymorphicBase { private static final long serialVersionUID = 1L; private IntIdEntity relation2; + private PolymorphicBase parent2; private Integer sub2Value; public PolymorphicSub2() { @@ -42,6 +43,14 @@ public IntIdEntity getRelation2() { public void setRelation2(IntIdEntity relation2) { this.relation2 = relation2; } + @ManyToOne(fetch = FetchType.LAZY) + public PolymorphicBase getParent2() { + return parent2; + } + + public void setParent2(PolymorphicBase parent1) { + this.parent2 = parent1; + } public Integer getSub2Value() { return sub2Value; diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/Project.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/Project.java index 2aa3548140..2abe63de60 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/Project.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/Project.java @@ -29,8 +29,7 @@ * @author Christian Beikov * @since 1.0 */ -// TODO: Re-enable when HHH-10265 is fixed -//@Entity(name = "Projects") +@Entity(name = "Projects") @Inheritance @DiscriminatorColumn(name = "category_type") public abstract class Project>> implements Serializable { diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/ProjectLeader.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/ProjectLeader.java index fd9f22c7ce..bc8e5c2402 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/ProjectLeader.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/ProjectLeader.java @@ -34,8 +34,7 @@ * @author Moritz Becker * @since 1.0 */ -//TODO: Re-enable when HHH-10265 is fixed -//@Entity +@Entity @Inheritance @DiscriminatorColumn(name = "project_leader_type") public abstract class ProjectLeader

>> implements Serializable { diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/SeniorProjectLeader.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/SeniorProjectLeader.java index 2876e00f4d..c1c21ee0d7 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/SeniorProjectLeader.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/SeniorProjectLeader.java @@ -23,7 +23,7 @@ * @author Christian Beikov * @since 1.0 */ -//@Entity +@Entity @DiscriminatorValue("S") public class SeniorProjectLeader extends ProjectLeader { diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/SmallProject.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/SmallProject.java index 0e89a020b3..55a7b25b80 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/SmallProject.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/SmallProject.java @@ -23,7 +23,7 @@ * @author Christian Beikov * @since 1.0 */ -//@Entity +@Entity @DiscriminatorValue("S") public class SmallProject extends Project { diff --git a/core/testsuite/src/main/resources/META-INF/persistence.xml b/core/testsuite/src/main/resources/META-INF/persistence.xml index 9e93d6f6f8..b9eb137f42 100644 --- a/core/testsuite/src/main/resources/META-INF/persistence.xml +++ b/core/testsuite/src/main/resources/META-INF/persistence.xml @@ -14,16 +14,17 @@ 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. --> - + com.blazebit.persistence.testsuite.entity.Document com.blazebit.persistence.testsuite.entity.DocumentNodeCTE - com.blazebit.persistence.testsuite.entity.EmbeddableTestEntity + com.blazebit.persistence.testsuite.entity.EmbeddableTestEntity com.blazebit.persistence.testsuite.entity.IdHolderCTE com.blazebit.persistence.testsuite.entity.IntIdEntity - + com.blazebit.persistence.testsuite.entity.JuniorProjectLeader com.blazebit.persistence.testsuite.entity.KeysetEntity - + com.blazebit.persistence.testsuite.entity.LargeProject com.blazebit.persistence.testsuite.entity.LocalizedEntity com.blazebit.persistence.testsuite.entity.Order com.blazebit.persistence.testsuite.entity.OrderPosition @@ -32,13 +33,12 @@ limitations under the License. com.blazebit.persistence.testsuite.entity.OrderPositionId com.blazebit.persistence.testsuite.entity.Ownable com.blazebit.persistence.testsuite.entity.Person - - - - + + com.blazebit.persistence.testsuite.entity.Project + com.blazebit.persistence.testsuite.entity.ProjectLeader com.blazebit.persistence.testsuite.entity.RecursiveEntity - - + com.blazebit.persistence.testsuite.entity.SeniorProjectLeader + com.blazebit.persistence.testsuite.entity.SmallProject com.blazebit.persistence.testsuite.entity.TestCTE com.blazebit.persistence.testsuite.entity.Version com.blazebit.persistence.testsuite.entity.Workflow diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/InheritanceTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/InheritanceTest.java index 922ed10cbd..d3600c8ae7 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/InheritanceTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/InheritanceTest.java @@ -50,7 +50,7 @@ protected Class[] getEntityClasses() { } @Test - @Ignore("There is a bug in the metamodel generation when used in eclipse so we skip this test for now") + // NOTE: There is a bug in the metamodel generation when used in eclipse: HHH-10265 public void testInheritanceWithEntityName() { @SuppressWarnings("rawtypes") CriteriaBuilder cb = cbf.create(em, Project.class, "p"); @@ -60,7 +60,7 @@ public void testInheritanceWithEntityName() { } @Test - @Ignore("There is a bug in the metamodel generation when used in eclipse so we skip this test for now") + // NOTE: There is a bug in the metamodel generation when used in eclipse: HHH-10265 public void testJoinPolymorphicEntity() { @SuppressWarnings("rawtypes") CriteriaBuilder cb = cbf.create(em, Project.class, "p") @@ -71,7 +71,7 @@ public void testJoinPolymorphicEntity() { } @Test - @Ignore("There is a bug in the metamodel generation when used in eclipse so we skip this test for now") + // NOTE: There is a bug in the metamodel generation when used in eclipse: HHH-10265 public void testImplicitJoinPolymorphicEntity() { CriteriaBuilder cb = cbf.create(em, Long.class) .from(Project.class, "p") diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/PaginationTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/PaginationTest.java index a5661060b7..c3975d5bc6 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/PaginationTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/PaginationTest.java @@ -363,7 +363,9 @@ public void testOrderBySubquery() { } @Test - @Ignore("We haven't handeled SIZE transformations for all clauses yet") + // NOTE: We haven't handeled SIZE transformations for all clauses yet + // TODO: 188 + @Ignore("#188") public void testOrderBySize() { PaginatedCriteriaBuilder cb = cbf.create(em, Tuple.class).from(Document.class, "d") .select("SIZE(d.contacts)") diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/PolymorphicPropertyTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/PolymorphicPropertyTest.java index be14a5936b..9a281f47bd 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/PolymorphicPropertyTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/PolymorphicPropertyTest.java @@ -52,8 +52,9 @@ protected Class[] getEntityClasses() { } @Test - @Ignore("Actually this kind of query is dangerous because hibernate chooses one subtype of PolymorphicPropertyBase and goes on with that assumption instead of searching for the subtype that fits") +// @Ignore("Actually this kind of query is dangerous because hibernate chooses one subtype of PolymorphicPropertyBase and goes on with that assumption instead of searching for the subtype that fits") public void testSelectSubProperty() { + // NOTE: Maybe this test should be a negative test, as a usage like this should not be supported but only by using treat CriteriaBuilder cb = cbf.create(em, PolymorphicPropertyBase.class, "propBase"); cb.select("propBase.base.relation1"); cb.where("TYPE(base)").eq(PolymorphicSub1.class); @@ -63,16 +64,15 @@ public void testSelectSubProperty() { } // NOTE: This is JPA 2.1 specific - // TODO: implement treat support @Test - @Ignore("Treat support is not yet implemented. Also note that hibernate does not fully support TREAT: https://hibernate.atlassian.net/browse/HHH-9345") public void testSelectSubPropertyWithTreat() { -// CriteriaBuilder cb = cbf.create(em, PolymorphicPropertyBase.class, "propBase"); -// cb.select("TREAT(propBase.base AS PolymorphicSub1).relation1"); -// cb.where("TYPE(base)").eq(PolymorphicSub1.class); - String expectedQuery = "SELECT relation1_1 FROM PolymorphicPropertyBase propBase LEFT JOIN TREAT(propBase.base AS PolymorphicSub1) base_1 LEFT JOIN base_1.relation1 relation1_1 WHERE TYPE(base_1) = :param_0"; -// assertEquals(expectedQuery, cb.getQueryString()); -// cb.getResultList(); - em.createQuery(expectedQuery).getResultList(); + CriteriaBuilder cb = cbf.create(em, PolymorphicPropertyBase.class, "propBase"); + cb.select("relation1"); + cb.leftJoin("TREAT(propBase.base AS PolymorphicSub1)", "base1"); + cb.leftJoin("base1.relation1", "relation1"); + cb.where("TYPE(base1)").eq(PolymorphicSub1.class); + String expectedQuery = "SELECT relation1 FROM PolymorphicPropertyBase propBase LEFT JOIN " + treatJoin("propBase.base", PolymorphicSub1.class) + " base1 LEFT JOIN base1.relation1 relation1 WHERE TYPE(base1) = :param_0"; + assertEquals(expectedQuery, cb.getQueryString()); + cb.getResultList(); } } diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/TreatTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/TreatTest.java new file mode 100644 index 0000000000..7b7122f01c --- /dev/null +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/TreatTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2014 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.testsuite; + +import com.blazebit.persistence.CriteriaBuilder; +import com.blazebit.persistence.JoinType; +import com.blazebit.persistence.PaginatedCriteriaBuilder; +import com.blazebit.persistence.testsuite.base.category.NoDB2; +import com.blazebit.persistence.testsuite.base.category.NoDatanucleus; +import com.blazebit.persistence.testsuite.base.category.NoHibernate; +import com.blazebit.persistence.testsuite.base.category.NoHibernate42; +import com.blazebit.persistence.testsuite.entity.*; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import javax.persistence.EntityTransaction; +import javax.persistence.Tuple; +import java.util.List; + +import static com.googlecode.catchexception.CatchException.verifyException; +import static org.junit.Assert.assertEquals; + +/** + * + * @author Christian Beikov + * @since 1.2 + */ +public class TreatTest extends AbstractCoreTest { + + @Override + protected Class[] getEntityClasses() { + return new Class[] { + IntIdEntity.class, + PolymorphicBase.class, + PolymorphicSub1.class, + PolymorphicSub2.class + }; + } + + @Test + public void implicitJoinTreatedRoot() { + CriteriaBuilder criteria = cbf.create(em, Integer.class); + criteria.from(PolymorphicBase.class, "p"); + criteria.select("TREAT(p AS PolymorphicSub1).sub1Value"); + assertEquals("SELECT " + treatRoot("p", PolymorphicSub1.class, "sub1Value") + " FROM PolymorphicBase p", criteria.getQueryString()); + criteria.getResultList(); + } + + @Test + public void implicitJoinTreatedRelation() { + CriteriaBuilder criteria = cbf.create(em, Integer.class); + criteria.from(PolymorphicBase.class, "p"); + criteria.select("TREAT(p.parent AS PolymorphicSub1).sub1Value"); + assertEquals("SELECT " + treatRoot("parent_1", PolymorphicSub1.class, "sub1Value") + " FROM PolymorphicBase p LEFT JOIN p.parent parent_1", criteria.getQueryString()); + criteria.getResultList(); + } + + @Test + public void joinTreatedRelation() { + CriteriaBuilder criteria = cbf.create(em, Integer.class); + criteria.from(PolymorphicBase.class, "p"); + criteria.select("polymorphicSub1.sub1Value"); + criteria.innerJoin("TREAT(p.parent AS PolymorphicSub1)", "polymorphicSub1"); + assertEquals("SELECT polymorphicSub1.sub1Value FROM PolymorphicBase p JOIN " + treatJoin("p.parent", PolymorphicSub1.class) + " polymorphicSub1", criteria.getQueryString()); + criteria.getResultList(); + } + + @Test + // TODO: This is an extension of the treat grammar. Maybe we should render a cross/left join for the root path treat and then just treat on the other alias? + public void treatJoinTreatedRootRelation() { + CriteriaBuilder criteria = cbf.create(em, Integer.class); + criteria.from(PolymorphicBase.class, "p"); + criteria.select("polymorphicSub1.sub1Value"); + criteria.innerJoin("TREAT(TREAT(p AS PolymorphicSub1).parent1 AS PolymorphicSub1)", "polymorphicSub1"); + assertEquals("SELECT polymorphicSub1.sub1Value FROM PolymorphicBase p JOIN " + treatJoin(treatJoin("p", PolymorphicSub1.class) + ".parent1", PolymorphicSub1.class) + " polymorphicSub1", criteria.getQueryString()); + criteria.getResultList(); + } + + @Test + // TODO: This is an extension of the treat grammar. Maybe we should render a cross/left join for the root path treat and then just treat on the other alias? + public void joinTreatedRoot() { + CriteriaBuilder criteria = cbf.create(em, Integer.class); + criteria.from(PolymorphicBase.class, "p"); + criteria.select("parent1.name"); + criteria.innerJoin("TREAT(p AS PolymorphicSub1).parent1", "parent1"); + assertEquals("SELECT parent1.name FROM PolymorphicBase p JOIN " + treatJoin("p", PolymorphicSub1.class, "parent1") + " parent1", criteria.getQueryString()); + criteria.getResultList(); + } +} diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/WhereTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/WhereTest.java index 71238b7403..ba4ce227f7 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/WhereTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/WhereTest.java @@ -496,8 +496,9 @@ public void testWhereRightSideSimpleCase() { assertEquals(expected, crit.getQueryString()); crit.getResultList(); } - - @Ignore("Not yet implemented") + + // TODO: #188 + @Ignore("#188") @Test public void testWhereSizeSingle() { CriteriaBuilder crit = cbf.create(em, Document.class, "d"); @@ -507,8 +508,9 @@ public void testWhereSizeSingle() { final String expected = "SELECT d.id, d.name FROM Document d LEFT JOIN d.versions versions_1 GROUP BY d.id, d.name HAVING COUNT(versions_1) > 2"; assertEquals(expected, crit.getQueryString()); } - - @Ignore("Not yet implemented") + + // TODO: #188 + @Ignore("#188") @Test public void testWhereSizeMultiple() { CriteriaBuilder crit = cbf.create(em, Document.class, "d"); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/CollectionJoinMappingGathererExpressionVisitor.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/CollectionJoinMappingGathererExpressionVisitor.java index ddd9a4d979..86bba994e7 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/CollectionJoinMappingGathererExpressionVisitor.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/CollectionJoinMappingGathererExpressionVisitor.java @@ -25,12 +25,7 @@ import javax.persistence.metamodel.PluralAttribute; import javax.persistence.metamodel.Type; -import com.blazebit.persistence.impl.expression.ArrayExpression; -import com.blazebit.persistence.impl.expression.Expression; -import com.blazebit.persistence.impl.expression.PathElementExpression; -import com.blazebit.persistence.impl.expression.PathExpression; -import com.blazebit.persistence.impl.expression.PropertyExpression; -import com.blazebit.persistence.impl.expression.VisitorAdapter; +import com.blazebit.persistence.impl.expression.*; import com.blazebit.persistence.impl.predicate.IsEmptyPredicate; import com.blazebit.persistence.impl.predicate.MemberOfPredicate; @@ -118,7 +113,7 @@ public void visit(PathExpression expression) { } } - @Override + @Override public void visit(IsEmptyPredicate predicate) { // NOTE: not sure if we should skip expressions of this predicate super.visit(predicate); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/TargetResolvingExpressionVisitor.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/PathTargetResolvingExpressionVisitor.java similarity index 93% rename from entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/TargetResolvingExpressionVisitor.java rename to entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/PathTargetResolvingExpressionVisitor.java index f2e5b4bf7a..4d946ed95e 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/TargetResolvingExpressionVisitor.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/PathTargetResolvingExpressionVisitor.java @@ -38,15 +38,21 @@ import com.blazebit.persistence.impl.predicate.MemberOfPredicate; import com.blazebit.persistence.impl.predicate.NotPredicate; import com.blazebit.persistence.impl.predicate.OrPredicate; +import com.blazebit.persistence.view.impl.metamodel.EntityMetamodel; import com.blazebit.reflection.ReflectionUtils; +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.Metamodel; + /** + * A visitor that can determine possible target types of a path expression. * * @author Christian Beikov * @since 1.0 */ -public class TargetResolvingExpressionVisitor implements Expression.Visitor { +public class PathTargetResolvingExpressionVisitor implements Expression.Visitor { + private final EntityMetamodel metamodel; private PathPosition currentPosition; private List pathPositions; @@ -101,7 +107,8 @@ PathPosition copy() { } } - public TargetResolvingExpressionVisitor(Class startClass) { + public PathTargetResolvingExpressionVisitor(EntityMetamodel metamodel, Class startClass) { + this.metamodel = metamodel; this.pathPositions = new ArrayList(); this.pathPositions.add(currentPosition = new PathPosition(startClass, null)); } @@ -191,6 +198,14 @@ public void visit(ArrayExpression expression) { expression.getBase().accept(this); } + @Override + public void visit(TreatExpression expression) { + EntityType type = metamodel.getEntity(expression.getType()); + currentPosition.setMethod(null); + currentPosition.setCurrentClass(type.getJavaType()); + currentPosition.setValueClass(type.getJavaType()); + } + @Override public void visit(ParameterExpression expression) { invalid(expression); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/MetamodelTargetResolvingExpressionVisitor.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/ScalarTargetResolvingExpressionVisitor.java similarity index 95% rename from entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/MetamodelTargetResolvingExpressionVisitor.java rename to entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/ScalarTargetResolvingExpressionVisitor.java index aaef421bd5..4c3600a911 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/MetamodelTargetResolvingExpressionVisitor.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/ScalarTargetResolvingExpressionVisitor.java @@ -23,21 +23,24 @@ import java.util.Map; import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.ManagedType; import javax.persistence.metamodel.Metamodel; import com.blazebit.persistence.impl.expression.*; +import com.blazebit.persistence.view.impl.metamodel.EntityMetamodel; import com.blazebit.reflection.ReflectionUtils; /** + * A visitor that can determine possible target types of a scalar expressions. * * @author Christian Beikov * @since 1.0 */ -public class MetamodelTargetResolvingExpressionVisitor extends VisitorAdapter { +public class ScalarTargetResolvingExpressionVisitor extends VisitorAdapter { private final ManagedType managedType; - private final Metamodel metamodel; + private final EntityMetamodel metamodel; private boolean parametersAllowed; private PathPosition currentPosition; private List pathPositions; @@ -103,7 +106,7 @@ PathPosition copy() { } } - public MetamodelTargetResolvingExpressionVisitor(ManagedType managedType, Metamodel metamodel) { + public ScalarTargetResolvingExpressionVisitor(ManagedType managedType, EntityMetamodel metamodel) { this.managedType = managedType; this.metamodel = metamodel; this.parametersAllowed = false; @@ -279,6 +282,14 @@ public void visit(ArrayExpression expression) { expression.getBase().accept(this); } + @Override + public void visit(TreatExpression expression) { + EntityType type = metamodel.getEntity(expression.getType()); + currentPosition.setMethod(null); + currentPosition.setCurrentClass(type.getJavaType()); + currentPosition.setValueClass(type.getJavaType()); + } + @Override public void visit(ParameterExpression expression) { // We can't infer a type here diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/UpdatableExpressionVisitor.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/UpdatableExpressionVisitor.java index 2b171d77e6..d10aeeaded 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/UpdatableExpressionVisitor.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/UpdatableExpressionVisitor.java @@ -155,6 +155,12 @@ public void visit(ArrayExpression expression) { invalid(expression); } + @Override + public void visit(TreatExpression expression) { + // NOTE: JPQL does not support treat in the SET clause + invalid(expression); + } + @Override public void visit(ParameterExpression expression) { invalid(expression); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java index e5c2765b48..80656f5af9 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java @@ -35,9 +35,9 @@ import com.blazebit.persistence.view.MappingSubquery; import com.blazebit.persistence.view.SubqueryProvider; import com.blazebit.persistence.view.impl.CollectionJoinMappingGathererExpressionVisitor; -import com.blazebit.persistence.view.impl.MetamodelTargetResolvingExpressionVisitor; +import com.blazebit.persistence.view.impl.ScalarTargetResolvingExpressionVisitor; import com.blazebit.persistence.view.impl.UpdatableExpressionVisitor; -import com.blazebit.persistence.view.impl.MetamodelTargetResolvingExpressionVisitor.TargetType; +import com.blazebit.persistence.view.impl.ScalarTargetResolvingExpressionVisitor.TargetType; import com.blazebit.persistence.view.metamodel.Attribute; import com.blazebit.persistence.view.metamodel.ManagedViewType; import com.blazebit.persistence.view.metamodel.PluralAttribute; @@ -128,7 +128,7 @@ public Set getCollectionJoinMappings(ManagedType managedType, Metamod return mappings; } - public String checkAttribute(ManagedType managedType, Map, ManagedViewType> managedViews, ExpressionFactory expressionFactory, Metamodel metamodel) { + public String checkAttribute(ManagedType managedType, Map, ManagedViewType> managedViews, ExpressionFactory expressionFactory, EntityMetamodel metamodel) { if (mapping == null || queryParameter) { // Subqueries and parameters can't be checked return null; @@ -177,7 +177,7 @@ public String checkAttribute(ManagedType managedType, Map, ManagedVi expressionType = subviewType.getEntityClass(); } - MetamodelTargetResolvingExpressionVisitor visitor = new MetamodelTargetResolvingExpressionVisitor(managedType, metamodel); + ScalarTargetResolvingExpressionVisitor visitor = new ScalarTargetResolvingExpressionVisitor(managedType, metamodel); try { expressionFactory.createSimpleExpression(mapping).accept(visitor); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/EmbeddableViewTypeImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/EmbeddableViewTypeImpl.java index 305b0513a2..0d9e54291e 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/EmbeddableViewTypeImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/EmbeddableViewTypeImpl.java @@ -20,6 +20,7 @@ import javax.persistence.metamodel.Metamodel; import com.blazebit.annotation.AnnotationUtils; +import com.blazebit.persistence.impl.expression.ExpressionFactory; import com.blazebit.persistence.view.EmbeddableEntityView; import com.blazebit.persistence.view.metamodel.EmbeddableViewType; @@ -31,8 +32,8 @@ public class EmbeddableViewTypeImpl extends ManagedViewTypeImpl implements EmbeddableViewType { - public EmbeddableViewTypeImpl(Class clazz, Set> entityViews, Metamodel metamodel) { - super(clazz, getEntityClass(clazz, metamodel), entityViews); + public EmbeddableViewTypeImpl(Class clazz, Set> entityViews, EntityMetamodel metamodel, ExpressionFactory expressionFactory) { + super(clazz, getEntityClass(clazz, metamodel), entityViews, metamodel, expressionFactory); } private static Class getEntityClass(Class clazz, Metamodel metamodel) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/EntityMetamodel.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/EntityMetamodel.java new file mode 100644 index 0000000000..3ee581fb24 --- /dev/null +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/EntityMetamodel.java @@ -0,0 +1,80 @@ +package com.blazebit.persistence.view.impl.metamodel; + +import javax.persistence.metamodel.EmbeddableType; +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.ManagedType; +import javax.persistence.metamodel.Metamodel; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * This is a wrapper around the JPA {@link javax.persistence.metamodel.Metamodel} allows additionally efficient access by other attributes than a Class. + * + * @author Christian Beikov + * @since 1.2 + */ +public class EntityMetamodel implements Metamodel { + + private final Metamodel delegate; + private final Map> entityNameMap; + private final Map, ManagedType> classMap; + + public EntityMetamodel(Metamodel delegate) { + this.delegate = delegate; + Set> managedTypes = delegate.getManagedTypes(); + Map> nameToType = new HashMap>(managedTypes.size()); + Map, ManagedType> classToType = new HashMap, ManagedType>(managedTypes.size()); + + for (ManagedType t : managedTypes) { + if (t instanceof EntityType) { + EntityType e = (EntityType) t; + nameToType.put(e.getName(), e); + } + classToType.put(t.getJavaType(), t); + } + + this.entityNameMap = Collections.unmodifiableMap(nameToType); + this.classMap = Collections.unmodifiableMap(classToType); + } + + @Override + public EntityType entity(Class cls) { + return delegate.entity(cls); + } + + public EntityType getEntity(String name) { + return entityNameMap.get(name); + } + + @Override + public ManagedType managedType(Class cls) { + return delegate.managedType(cls); + } + + @SuppressWarnings({ "unchecked" }) + public ManagedType getManagedType(Class cls) { + return (ManagedType) classMap.get(cls); + } + + @Override + public EmbeddableType embeddable(Class cls) { + return delegate.embeddable(cls); + } + + @Override + public Set> getManagedTypes() { + return delegate.getManagedTypes(); + } + + @Override + public Set> getEntities() { + return delegate.getEntities(); + } + + @Override + public Set> getEmbeddables() { + return delegate.getEmbeddables(); + } +} diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ManagedViewTypeImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ManagedViewTypeImpl.java index af203e5acf..5342a1b274 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ManagedViewTypeImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ManagedViewTypeImpl.java @@ -64,7 +64,7 @@ public abstract class ManagedViewTypeImpl implements ManagedViewType { protected final Map attributeFilters; @SuppressWarnings("unchecked") - public ManagedViewTypeImpl(Class clazz, Class entityClass, Set> entityViews) { + public ManagedViewTypeImpl(Class clazz, Class entityClass, Set> entityViews, EntityMetamodel metamodel, ExpressionFactory expressionFactory) { this.javaType = (Class) clazz; this.entityClass = entityClass; @@ -82,7 +82,7 @@ public ManagedViewTypeImpl(Class clazz, Class entityClass, Set clazz, Class entityClass, Set mappingConstructor = new MappingConstructorImpl(this, constructorName, (Constructor) constructor, entityViews); + MappingConstructorImpl mappingConstructor = new MappingConstructorImpl(this, constructorName, (Constructor) constructor, entityViews, metamodel, expressionFactory); constructors.put(new ParametersKey(constructor.getParameterTypes()), mappingConstructor); constructorIndex.put(constructorName, mappingConstructor); } } - private void handleMethod(Method method, Set> entityViews) { + private void handleMethod(Method method, Set> entityViews, EntityMetamodel metamodel, ExpressionFactory expressionFactory) { String attributeName = AbstractMethodAttribute.validate(this, method); if (attributeName != null && !attributes.containsKey(attributeName)) { - AbstractMethodAttribute attribute = createMethodAttribute(this, method, entityViews); + AbstractMethodAttribute attribute = createMethodAttribute(this, method, entityViews, metamodel, expressionFactory); if (attribute != null) { attributes.put(attribute.getName(), attribute); addAttributeFilters(attribute); @@ -115,7 +115,7 @@ private void handleMethod(Method method, Set> entityViews) { } } - public void checkAttributes(Map, ManagedViewType> managedViews, ExpressionFactory expressionFactory, Metamodel metamodel, Set errors) { + public void checkAttributes(Map, ManagedViewType> managedViews, ExpressionFactory expressionFactory, EntityMetamodel metamodel, Set errors) { ManagedType managedType = metamodel.managedType(entityClass); Map> collectionMappings = new HashMap>(); @@ -196,7 +196,7 @@ private void addAttributeFilters(AbstractMethodAttribute attribute } // If you change something here don't forget to also update MappingConstructorImpl#createMethodAttribute - private static AbstractMethodAttribute createMethodAttribute(ManagedViewType viewType, Method method, Set> entityViews) { + private static AbstractMethodAttribute createMethodAttribute(ManagedViewType viewType, Method method, Set> entityViews, EntityMetamodel metamodel, ExpressionFactory expressionFactory) { Annotation mapping = AbstractMethodAttribute.getMapping(viewType, method); if (mapping == null) { return null; @@ -212,7 +212,7 @@ private void addAttributeFilters(AbstractMethodAttribute attribute if (Collection.class == attributeType) { return new MethodMappingCollectionAttributeImpl(viewType, method, mapping, entityViews); } else if (List.class == attributeType) { - return new MethodMappingListAttributeImpl(viewType, method, mapping, entityViews); + return new MethodMappingListAttributeImpl(viewType, method, mapping, entityViews, metamodel, expressionFactory); } else if (Set.class == attributeType || SortedSet.class == attributeType || NavigableSet.class == attributeType) { return new MethodMappingSetAttributeImpl(viewType, method, mapping, entityViews); } else if (Map.class == attributeType || SortedMap.class == attributeType || NavigableMap.class == attributeType) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MappingConstructorImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MappingConstructorImpl.java index 9c44f7f4b9..c921fb858a 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MappingConstructorImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MappingConstructorImpl.java @@ -55,7 +55,7 @@ public class MappingConstructorImpl implements MappingConstructor { private final Constructor javaConstructor; private final List> parameters; - public MappingConstructorImpl(ManagedViewType viewType, String name, Constructor constructor, Set> entityViews) { + public MappingConstructorImpl(ManagedViewType viewType, String name, Constructor constructor, Set> entityViews, EntityMetamodel metamodel, ExpressionFactory expressionFactory) { this.name = name; this.declaringType = viewType; this.javaConstructor = constructor; @@ -69,14 +69,14 @@ public MappingConstructorImpl(ManagedViewType viewType, String name, Construc List> parameters = new ArrayList>(parameterCount); for (int i = 0; i < parameterCount; i++) { AbstractParameterAttribute.validate(this, i); - AbstractParameterAttribute parameter = createParameterAttribute(this, i, entityViews); + AbstractParameterAttribute parameter = createParameterAttribute(this, i, entityViews, metamodel, expressionFactory); parameters.add(parameter); } this.parameters = Collections.unmodifiableList(parameters); } - public void checkParameters(ManagedType managedType, Map, ManagedViewType> managedViews, ExpressionFactory expressionFactory, Metamodel metamodel, Map> collectionMappings, Set errors) { + public void checkParameters(ManagedType managedType, Map, ManagedViewType> managedViews, ExpressionFactory expressionFactory, EntityMetamodel metamodel, Map> collectionMappings, Set errors) { for (AbstractParameterAttribute parameter : parameters) { String error = parameter.checkAttribute(managedType, managedViews, expressionFactory, metamodel); @@ -107,7 +107,7 @@ public static String validate(ManagedViewType viewType, Constructor c) { } // If you change something here don't forget to also update ViewTypeImpl#createMethodAttribute - private static AbstractParameterAttribute createParameterAttribute(MappingConstructor constructor, int index, Set> entityViews) { + private static AbstractParameterAttribute createParameterAttribute(MappingConstructor constructor, int index, Set> entityViews, EntityMetamodel metamodel, ExpressionFactory expressionFactory) { Annotation mapping = AbstractParameterAttribute.getMapping(constructor, index); if (mapping == null) { return null; @@ -138,7 +138,7 @@ public static String validate(ManagedViewType viewType, Constructor c) { if (Collection.class == attributeType) { return new ParameterMappingCollectionAttributeImpl(constructor, index, mapping, entityViews); } else if (List.class == attributeType) { - return new ParameterMappingListAttributeImpl(constructor, index, mapping, entityViews); + return new ParameterMappingListAttributeImpl(constructor, index, mapping, entityViews, metamodel, expressionFactory); } else if (Set.class == attributeType || SortedSet.class == attributeType || NavigableSet.class == attributeType) { return new ParameterMappingSetAttributeImpl(constructor, index, mapping, entityViews); } else if (Map.class == attributeType || SortedMap.class == attributeType || NavigableMap.class == attributeType) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MetamodelUtils.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MetamodelUtils.java index a4b531a025..c8041ab2cc 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MetamodelUtils.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MetamodelUtils.java @@ -38,7 +38,7 @@ import com.blazebit.persistence.view.Mapping; import com.blazebit.persistence.view.MappingParameter; import com.blazebit.persistence.view.MappingSubquery; -import com.blazebit.persistence.view.impl.TargetResolvingExpressionVisitor; +import com.blazebit.persistence.view.impl.PathTargetResolvingExpressionVisitor; import com.blazebit.persistence.view.metamodel.MappingConstructor; import com.blazebit.reflection.ReflectionUtils; @@ -49,8 +49,6 @@ */ public final class MetamodelUtils { - private static final ExpressionFactory expressionFactory = new SimpleCachingExpressionFactory(new ExpressionFactoryImpl(new HashSet())); - public static CollectionMapping getCollectionMapping(MappingConstructor mappingConstructor, int index) { return getCollectionMapping(findAnnotation(mappingConstructor, index, CollectionMapping.class)); } @@ -109,7 +107,7 @@ public static Comparator getComparator(Class> clazz) { } } - public static boolean isIndexedList(Class entityClass, Annotation mappingAnnotation) { + public static boolean isIndexedList(EntityMetamodel metamodel, ExpressionFactory expressionFactory, Class entityClass, Annotation mappingAnnotation) { if (mappingAnnotation instanceof MappingSubquery || mappingAnnotation instanceof MappingParameter) { return false; } @@ -123,8 +121,8 @@ public static boolean isIndexedList(Class entityClass, Annotation mappingAnno } else { throw new IllegalArgumentException("Unkown mapping encountered: " + mappingAnnotation); } - - TargetResolvingExpressionVisitor visitor = new TargetResolvingExpressionVisitor(entityClass); + + PathTargetResolvingExpressionVisitor visitor = new PathTargetResolvingExpressionVisitor(metamodel, entityClass); expressionFactory.createSimpleExpression(mapping).accept(visitor); Map> possibleTargets = visitor.getPossibleTargets(); Iterator>> iter = possibleTargets.entrySet().iterator(); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodMappingListAttributeImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodMappingListAttributeImpl.java index 1768f682a6..8ecf2299e5 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodMappingListAttributeImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodMappingListAttributeImpl.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Set; +import com.blazebit.persistence.impl.expression.ExpressionFactory; import com.blazebit.persistence.view.metamodel.ListAttribute; import com.blazebit.persistence.view.metamodel.ManagedViewType; @@ -32,13 +33,13 @@ public class MethodMappingListAttributeImpl extends AbstractMethodMappingP private final boolean isIndexed; - public MethodMappingListAttributeImpl(ManagedViewType viewType, Method method, Annotation mapping, Set> entityViews) { + public MethodMappingListAttributeImpl(ManagedViewType viewType, Method method, Annotation mapping, Set> entityViews, EntityMetamodel metamodel, ExpressionFactory expressionFactory) { super(viewType, method, mapping, entityViews, false); if (isIgnoreIndex()) { this.isIndexed = false; } else { - this.isIndexed = MetamodelUtils.isIndexedList(viewType.getEntityClass(), mapping); + this.isIndexed = MetamodelUtils.isIndexedList(metamodel, expressionFactory, viewType.getEntityClass(), mapping); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ParameterMappingListAttributeImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ParameterMappingListAttributeImpl.java index 8b29d1f95c..3cb6d36118 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ParameterMappingListAttributeImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ParameterMappingListAttributeImpl.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Set; +import com.blazebit.persistence.impl.expression.ExpressionFactory; import com.blazebit.persistence.view.metamodel.ListAttribute; import com.blazebit.persistence.view.metamodel.MappingConstructor; @@ -31,13 +32,13 @@ public class ParameterMappingListAttributeImpl extends AbstractParameterMa private final boolean isIndexed; - public ParameterMappingListAttributeImpl(MappingConstructor mappingConstructor, int index, Annotation mapping, Set> entityViews) { + public ParameterMappingListAttributeImpl(MappingConstructor mappingConstructor, int index, Annotation mapping, Set> entityViews, EntityMetamodel metamodel, ExpressionFactory expressionFactory) { super(mappingConstructor, index, mapping, entityViews, false); if (isIgnoreIndex()) { this.isIndexed = false; } else { - this.isIndexed = MetamodelUtils.isIndexedList(mappingConstructor.getDeclaringType().getEntityClass(), mapping); + this.isIndexed = MetamodelUtils.isIndexedList(metamodel, expressionFactory, mappingConstructor.getDeclaringType().getEntityClass(), mapping); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMetamodelImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMetamodelImpl.java index bb0dc8e890..2a597ae50f 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMetamodelImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMetamodelImpl.java @@ -40,11 +40,13 @@ */ public class ViewMetamodelImpl implements ViewMetamodel { + private final EntityMetamodel metamodel; private final Map, ViewType> views; private final Map, EmbeddableViewType> embeddableViews; private final Map, ManagedViewType> managedViews; public ViewMetamodelImpl(Set> entityViews, boolean validateExpressions, ExpressionFactory expressionFactory, Metamodel metamodel) { + this.metamodel = new EntityMetamodel(metamodel); this.views = new HashMap, ViewType>(entityViews.size()); this.embeddableViews = new HashMap, EmbeddableViewType>(entityViews.size()); this.managedViews = new HashMap, ManagedViewType>(entityViews.size()); @@ -55,11 +57,11 @@ public ViewMetamodelImpl(Set> entityViews, boolean validateExpressions, ManagedViewType managedView; if (!isEmbeddableViewType(entityViewClass)) { - ViewType viewType = getViewType(entityViewClass, entityViews, metamodel); + ViewType viewType = getViewType(entityViewClass, entityViews, this.metamodel, expressionFactory); views.put(entityViewClass, viewType); managedView = viewType; } else { - EmbeddableViewType embeddableViewType = getEmbeddableViewType(entityViewClass, entityViews, metamodel); + EmbeddableViewType embeddableViewType = getEmbeddableViewType(entityViewClass, entityViews, this.metamodel, expressionFactory); embeddableViews.put(entityViewClass, embeddableViewType); managedView = embeddableViewType; } @@ -69,7 +71,7 @@ public ViewMetamodelImpl(Set> entityViews, boolean validateExpressions, if (validateExpressions) { for (ManagedViewType t : managedViews.values()) { - ((ManagedViewTypeImpl) t).checkAttributes(managedViews, expressionFactory, metamodel, errors); + ((ManagedViewTypeImpl) t).checkAttributes(managedViews, expressionFactory, this.metamodel, errors); } } @@ -151,12 +153,12 @@ private boolean isEmbeddableViewType(Class entityViewClass) { return AnnotationUtils.findAnnotation(entityViewClass, EmbeddableEntityView.class) != null; } - private ViewType getViewType(Class entityViewClass, Set> entityViews, Metamodel metamodel) { - return new ViewTypeImpl(entityViewClass, entityViews, metamodel); + private ViewType getViewType(Class entityViewClass, Set> entityViews, EntityMetamodel metamodel, ExpressionFactory expressionFactory) { + return new ViewTypeImpl(entityViewClass, entityViews, metamodel, expressionFactory); } - private EmbeddableViewType getEmbeddableViewType(Class entityViewClass, Set> entityViews, Metamodel metamodel) { - return new EmbeddableViewTypeImpl(entityViewClass, entityViews, metamodel); + private EmbeddableViewType getEmbeddableViewType(Class entityViewClass, Set> entityViews, EntityMetamodel metamodel, ExpressionFactory expressionFactory) { + return new EmbeddableViewTypeImpl(entityViewClass, entityViews, metamodel, expressionFactory); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewTypeImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewTypeImpl.java index f1d56acc9c..51b31c837f 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewTypeImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewTypeImpl.java @@ -24,6 +24,7 @@ import javax.persistence.metamodel.Metamodel; import com.blazebit.annotation.AnnotationUtils; +import com.blazebit.persistence.impl.expression.ExpressionFactory; import com.blazebit.persistence.view.EntityView; import com.blazebit.persistence.view.UpdatableEntityView; import com.blazebit.persistence.view.ViewFilter; @@ -46,8 +47,8 @@ public class ViewTypeImpl extends ManagedViewTypeImpl implements ViewType< private final MethodAttribute idAttribute; private final Map viewFilters; - public ViewTypeImpl(Class clazz, Set> entityViews, Metamodel metamodel) { - super(clazz, getEntityClass(clazz, metamodel), entityViews); + public ViewTypeImpl(Class clazz, Set> entityViews, EntityMetamodel metamodel, ExpressionFactory expressionFactory) { + super(clazz, getEntityClass(clazz, metamodel), entityViews, metamodel, expressionFactory); EntityView entityViewAnnot = AnnotationUtils.findAnnotation(clazz, EntityView.class);