From d0a7785ba588ed489d6afafa3b4a047e5510d0d4 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 31 Mar 2017 19:31:49 +0200 Subject: [PATCH] Fixed #120 - Implemented treat support. --- CHANGELOG.md | 1 + .../impl/AbstractCommonQueryBuilder.java | 5 +- .../impl/AbstractFullQueryBuilder.java | 2 +- .../persistence/impl/EntityMetamodelImpl.java | 56 +- .../impl/EntitySelectResolveVisitor.java | 4 +- .../persistence/impl/JoinAliasInfo.java | 4 + .../persistence/impl/JoinManager.java | 606 +++++++++--------- .../blazebit/persistence/impl/JoinNode.java | 307 +++++++-- .../blazebit/persistence/impl/JpaUtils.java | 6 +- .../impl/PaginatedCriteriaBuilderImpl.java | 16 +- .../impl/ResolvingQueryGenerator.java | 300 ++++++++- .../persistence/impl/SelectManager.java | 4 +- .../impl/TreatedJoinAliasInfo.java | 62 ++ .../transform/SizeTransformationVisitor.java | 25 +- .../impl/transform/SizeTransformerGroup.java | 2 +- .../persistence/impl/EntityMetamodel.java | 2 + .../impl/SimpleQueryGenerator.java | 20 +- .../expression/AbstractExpressionFactory.java | 7 +- .../impl/expression/PathExpression.java | 8 - .../impl/expression/PathReference.java | 2 - .../impl/expression/SimplePathReference.java | 17 +- .../impl/expression/SyntaxErrorException.java | 4 + .../CollectionJoinTestHibernate.java | 4 +- .../testsuite/DeletePolymorphicTest.java | 51 +- .../persistence/testsuite/OrderByTest.java | 8 +- .../testsuite/SelectPolymorphicTest.java | 27 +- .../testsuite/TablePerClassTest.java | 21 +- .../persistence/testsuite/TreatTest.java | 51 +- .../testsuite/UpdatePolymorphicTest.java | 34 +- .../builder/AbstractTreatVariationsTest.java | 8 +- .../datanucleus/DataNucleusJpaProvider.java | 32 +- .../eclipselink/EclipseLinkJpaProvider.java | 31 +- .../impl/hibernate/HibernateJpaProvider.java | 129 +++- .../impl/openjpa/OpenJPAJpaProvider.java | 27 +- 34 files changed, 1318 insertions(+), 565 deletions(-) create mode 100644 core/impl/src/main/java/com/blazebit/persistence/impl/TreatedJoinAliasInfo.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 339c90eb25..f9a6bcd662 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Not yet released * Various performance improvements * Support referring to `this` in all mapping types for putting values in embedded objects * Relaxed strict requirements for `@IdMapping` and removed `@EmbeddableEntityView` +* Full support for `TREAT` operator. Note that EclipseLink and DataNucleus only support very few basic usages ### Bug fixes diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCommonQueryBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCommonQueryBuilder.java index 6b8f95b0df..4c649de121 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCommonQueryBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCommonQueryBuilder.java @@ -687,6 +687,7 @@ public boolean isEmpty() { !fromClassExplicitelySet && joinManager.getRoots().size() == 1 && joinManager.getRoots().get(0).getNodes().isEmpty() + && joinManager.getRoots().get(0).getTreatedJoinNodes().isEmpty() && joinManager.getRoots().get(0).getEntityJoinNodes().isEmpty() ) ; @@ -1570,7 +1571,7 @@ protected List getEntityFunctionNodes(Query baseQuery) { String valuesAliases = node.getValuesAliases(); String valuesTableSqlAlias = cbf.getExtendedQuerySupport().getSqlAlias(em, baseQuery, node.getAlias()); - entityFunctionNodes.add(new EntityFunctionNode(valuesClause, valuesAliases, node.getPropertyClass(), valuesTableSqlAlias, node.getValueQuery())); + entityFunctionNodes.add(new EntityFunctionNode(valuesClause, valuesAliases, node.getType(), valuesTableSqlAlias, node.getValueQuery())); } return entityFunctionNodes; } @@ -1785,7 +1786,7 @@ protected void prepareAndCheck() { joinManager.acceptVisitor(new JoinNodeVisitor() { @Override public void visit(JoinNode node) { - Class cteType = node.getPropertyClass(); + Class cteType = node.getType(); // Except for VALUES clause from nodes, every cte type must be defined if (node.getValueQuery() == null && mainQuery.metamodel.getCte(cteType) != null) { if (mainQuery.cteManager.getCte(cteType) == null) { 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 94976ac7d9..e17f2a26f7 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 @@ -131,7 +131,7 @@ private void checkEntityId(Object entityId) { throw new IllegalArgumentException("Invalid null entity id given"); } - EntityType entityType = mainQuery.metamodel.entity(joinManager.getRootNodeOrFail("Paginated queries do not support multiple from clause elements!").getPropertyClass()); + EntityType entityType = mainQuery.metamodel.entity(joinManager.getRootNodeOrFail("Paginated queries do not support multiple from clause elements!").getType()); Attribute idAttribute = JpaUtils.getIdAttribute(entityType); Class idType = JpaUtils.resolveFieldClass(entityType.getJavaType(), idAttribute); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/EntityMetamodelImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/EntityMetamodelImpl.java index 9113d5409b..a1fc20e126 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/EntityMetamodelImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/EntityMetamodelImpl.java @@ -23,6 +23,7 @@ import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.BasicType; import javax.persistence.metamodel.EmbeddableType; import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.ManagedType; @@ -37,6 +38,8 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; /** * This is a wrapper around the JPA {@link Metamodel} allows additionally efficient access by other attributes than a Class. @@ -50,7 +53,8 @@ public class EntityMetamodelImpl implements EntityMetamodel { private final Map> entityNameMap; private final Map> entityTypes; private final Map>> enumTypes; - private final Map, ManagedType> classMap; + private final Map, Type> classMap; + private final ConcurrentMap, Type> basicTypeMap = new ConcurrentHashMap<>(); private final Map, ManagedType> cteMap; private final Map, Map>> typeAttributeColumnNameMap; private final Map, Map>> typeAttributeColumnTypeMap; @@ -61,7 +65,7 @@ public EntityMetamodelImpl(EntityManagerFactory emf, ExtendedQuerySupport extend Map> nameToType = new HashMap<>(managedTypes.size()); Map> entityTypes = new HashMap<>(managedTypes.size()); Map>> enumTypes = new HashMap<>(managedTypes.size()); - Map, ManagedType> classToType = new HashMap<>(managedTypes.size()); + Map, Type> classToType = new HashMap<>(managedTypes.size()); Map, ManagedType> cteToType = new HashMap<>(managedTypes.size()); Map, Map>> typeAttributeColumnNames = new HashMap<>(managedTypes.size()); Map, Map>> typeAttributeColumnTypeNames = new HashMap<>(managedTypes.size()); @@ -200,6 +204,14 @@ public EntityType entity(Class cls) { return delegate.entity(cls); } + public EntityType entity(String name) { + EntityType type = entityNameMap.get(name); + if (type == null) { + throw new IllegalArgumentException("Invalid entity type: " + name); + } + return type; + } + @Override public EntityType getEntity(String name) { return entityNameMap.get(name); @@ -223,6 +235,22 @@ public ManagedType managedType(Class cls) { return delegate.managedType(cls); } + @Override + @SuppressWarnings({ "unchecked" }) + public Type type(Class cls) { + Type type = classMap.get(cls); + if (type != null) { + return (Type) type; + } + + type = new BasicTypeImpl<>(cls); + Type oldType = basicTypeMap.putIfAbsent(cls, type); + if (oldType != null) { + type = oldType; + } + return (Type) type; + } + @Override public ManagedType managedType(String name) { ManagedType t = entityNameMap.get(name); @@ -233,15 +261,16 @@ public ManagedType managedType(String name) { return t; } - @SuppressWarnings({ "unchecked" }) @Override + @SuppressWarnings({ "unchecked" }) public ManagedType getManagedType(Class cls) { return (ManagedType) classMap.get(cls); } @Override + @SuppressWarnings({ "unchecked" }) public EntityType getEntity(Class cls) { - ManagedType type = classMap.get(cls); + Type type = classMap.get(cls); if (type == null || !(type instanceof EntityType)) { return null; } @@ -273,4 +302,23 @@ public Set> getEntities() { public Set> getEmbeddables() { return delegate.getEmbeddables(); } + + private static class BasicTypeImpl implements BasicType { + + private final Class cls; + + public BasicTypeImpl(Class cls) { + this.cls = cls; + } + + @Override + public PersistenceType getPersistenceType() { + return PersistenceType.BASIC; + } + + @Override + public Class getJavaType() { + return cls; + } + } } 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 967f56342e..be737af1d6 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 @@ -70,7 +70,7 @@ public void visit(PathExpression expression) { * selects here */ JoinNode baseNode = ((JoinNode) expression.getBaseNode()); - EntityType entityType = m.getEntity(baseNode.getPropertyClass()); + EntityType entityType = m.getEntity(baseNode.getType()); if (entityType == null) { // ignore if the expression is not an entity return; @@ -101,7 +101,7 @@ public int compare(Attribute o1, Attribute o2) { if (resolve) { PathExpression attrPath = new PathExpression(new ArrayList(expression.getExpressions())); - attrPath.setPathReference(new SimplePathReference(baseNode, attr.getName(), null, JpaUtils.resolveFieldClass(entityClass, attr))); + attrPath.setPathReference(new SimplePathReference(baseNode, attr.getName(), JpaUtils.resolveFieldClass(entityClass, attr))); pathExpressions.add(attrPath); } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinAliasInfo.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinAliasInfo.java index d6b2bdaaf1..eaa12a5d70 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinAliasInfo.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinAliasInfo.java @@ -65,6 +65,10 @@ public String getAbsolutePath() { } } + public void render(StringBuilder sb) { + sb.append(alias); + } + public boolean isImplicit() { return implicit; } 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 d8052a748b..64359c7bc8 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 @@ -65,6 +65,7 @@ import javax.persistence.metamodel.ManagedType; import javax.persistence.metamodel.MapAttribute; import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -138,21 +139,14 @@ void applyFrom(JoinManager joinManager) { private JoinNode applyFrom(JoinNode node) { String rootAlias = node.getAlias(); boolean implicit = node.getAliasInfo().isImplicit(); - Class clazz = node.getPropertyClass(); - String treatFunction = node.getValuesFunction(); - int valueCount = node.getValueCount(); - int attributeCount = node.getAttributeCount(); - Query valuesExampleQuery = node.getValueQuery(); - String valuesClause = node.getValuesClause(); - String valuesAliases = node.getValuesAliases(); JoinAliasInfo rootAliasInfo = new JoinAliasInfo(rootAlias, rootAlias, implicit, true, aliasManager); JoinNode rootNode; if (node.getCorrelationParent() != null) { - throw new IllegalArgumentException("Cloning subqueries not yet implemented!"); + throw new UnsupportedOperationException("Cloning subqueries not yet implemented!"); } else { - rootNode = new JoinNode(rootAliasInfo, clazz, treatFunction, valueCount, attributeCount, valuesExampleQuery, valuesClause, valuesAliases, null); + rootNode = node.cloneRootNode(rootAliasInfo); } rootAliasInfo.setJoinNode(rootNode); @@ -164,6 +158,10 @@ private JoinNode applyFrom(JoinNode node) { applyFrom(rootNode, treeNode); } + if (!node.getTreatedJoinNodes().isEmpty()) { + throw new UnsupportedOperationException("Cloning joins with treat joins is not yet implemented!"); + } + return rootNode; } @@ -180,10 +178,9 @@ private JoinNode applyFrom(JoinNode parent, JoinTreeNode treeNode, String alias, String currentJoinPath = parent.getAliasInfo().getAbsolutePath() + "." + treeNode.getRelationName(); JoinAliasInfo newAliasInfo = new JoinAliasInfo(alias, currentJoinPath, implicit, false, aliasManager); aliasManager.registerAliasInfo(newAliasInfo); - JoinNode node = new JoinNode(parent, treeNode, oldNode.getParentTreatType(), newAliasInfo, oldNode.getJoinType(), oldNode.getPropertyClass(), oldNode.getTreatType(), oldNode.getQualificationExpression()); + JoinNode node = oldNode.cloneJoinNode(parent, treeNode, newAliasInfo); newAliasInfo.setJoinNode(node); - node.setFetch(oldNode.isFetch()); if (oldNode.getOnPredicate() != null) { node.setOnPredicate(subqueryInitFactory.reattachSubqueries(oldNode.getOnPredicate().clone(true))); } @@ -192,6 +189,10 @@ private JoinNode applyFrom(JoinNode parent, JoinTreeNode treeNode, String alias, applyFrom(node, oldTreeNode); } + if (!oldNode.getTreatedJoinNodes().isEmpty()) { + throw new UnsupportedOperationException("Cloning joins with treat joins is not yet implemented!"); + } + return node; } @@ -252,7 +253,7 @@ private void visitKeyOrIndexExpression(PathExpression pathExpression) { } } - String addRootValues(Class clazz, Class valueClazz, String rootAlias, int valueCount, String treatFunction, String castedParameter, boolean identifiableReference) { + String addRootValues(Class clazz, Class valueClazz, String rootAlias, int valueCount, String valuesFunction, String castedParameter, boolean identifiableReference) { if (rootAlias == null) { throw new IllegalArgumentException("Illegal empty alias for the VALUES clause: " + clazz.getName()); } @@ -281,7 +282,7 @@ String addRootValues(Class clazz, Class valueClazz, String rootAlias, int ValueRetriever[] pathExpressions = new ValueRetriever[attributeSet.size()]; StringBuilder valuesSb = new StringBuilder(20 + valueCount * attributeSet.size() * 3); - Query valuesExampleQuery = getValuesExampleQuery(clazz, identifiableReference, rootAlias, treatFunction, castedParameter, attributeSet, parameterNames, pathExpressions, valuesSb, strategy, dummyTable); + Query valuesExampleQuery = getValuesExampleQuery(clazz, identifiableReference, rootAlias, valuesFunction, castedParameter, attributeSet, parameterNames, pathExpressions, valuesSb, strategy, dummyTable); parameterManager.registerValuesParameter(rootAlias, valueClazz, parameterNames, pathExpressions); String exampleQuerySql = mainQuery.cbf.getExtendedQuerySupport().getSql(mainQuery.em, valuesExampleQuery); @@ -317,7 +318,7 @@ String addRootValues(Class clazz, Class valueClazz, String rootAlias, int String valuesClause = valuesSb.toString(); JoinAliasInfo rootAliasInfo = new JoinAliasInfo(rootAlias, rootAlias, true, true, aliasManager); - JoinNode rootNode = new JoinNode(rootAliasInfo, clazz, treatFunction, valueCount, attributeSet.size(), valuesExampleQuery, valuesClause, valuesAliases, null); + JoinNode rootNode = JoinNode.createValuesRootNode(managedType, valuesFunction, valueCount, attributeSet.size(), valuesExampleQuery, valuesClause, valuesAliases, rootAliasInfo); rootAliasInfo.setJoinNode(rootNode); rootNodes.add(rootNode); // register root alias in aliasManager @@ -537,10 +538,10 @@ private Query getValuesExampleQuery(Class clazz, boolean identifiableReferenc return q; } - String addRoot(EntityType clazz, String rootAlias) { + String addRoot(EntityType entityType, String rootAlias) { if (rootAlias == null) { // TODO: not sure if other JPA providers support case sensitive queries like hibernate - StringBuilder sb = new StringBuilder(clazz.getName()); + StringBuilder sb = new StringBuilder(entityType.getName()); sb.setCharAt(0, Character.toLowerCase(sb.charAt(0))); String alias = sb.toString(); @@ -551,7 +552,7 @@ String addRoot(EntityType clazz, String rootAlias) { } } JoinAliasInfo rootAliasInfo = new JoinAliasInfo(rootAlias, rootAlias, true, true, aliasManager); - JoinNode rootNode = new JoinNode(null, null, null, rootAliasInfo, null, clazz.getJavaType(), null, null); + JoinNode rootNode = JoinNode.createRootNode(entityType, rootAliasInfo); rootAliasInfo.setJoinNode(rootNode); rootNodes.add(rootNode); // register root alias in aliasManager @@ -560,21 +561,69 @@ String addRoot(EntityType clazz, String rootAlias) { } String addRoot(String correlationPath, String rootAlias) { - // TODO: TREAT support is missing - String[] parts = correlationPath.split("\\."); + Expression expr = expressionFactory.createJoinPathExpression(correlationPath); + + PathElementExpression elementExpr; + EntityType treatEntityType = null; + JoinResult result; + JoinNode correlationParent = null; + if (expr instanceof PathExpression) { + PathExpression pathExpression = (PathExpression) expr; + + if (isJoinableSelectAlias(pathExpression, false, false)) { + throw new IllegalArgumentException("No select alias allowed in join path"); + } + + List pathElements = pathExpression.getExpressions(); + elementExpr = pathElements.get(pathElements.size() - 1); + if (pathElements.size() > 1) { + result = implicitJoin(null, pathExpression, null, 0, pathElements.size() - 1, true); + correlationParent = result.baseNode; + } else { + result = new JoinResult(null, null, null); + } + } else if (expr instanceof TreatExpression) { + TreatExpression treatExpression = (TreatExpression) expr; + 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, null, 0, pathElements.size() - 1, true); + correlationParent = result.baseNode; + treatEntityType = metamodel.entity(treatExpression.getType()); + } else { + throw new IllegalArgumentException("Unexpected expression type[" + expression.getClass().getSimpleName() + "] in treat expression: " + treatExpression); + } + } else { + throw new IllegalArgumentException("Correlation join path [" + correlationPath + "] is not a valid join path"); + } + + if (elementExpr instanceof ArrayExpression) { + throw new IllegalArgumentException("Array expressions are not allowed!"); + } + + if (correlationParent == null) { + correlationParent = getRootNodeOrFail("Could not join correlation path [", correlationPath, "] because it did not use an absolute path but multiple root nodes are available!"); + } + + if (correlationParent.getAliasInfo().getAliasOwner() == aliasManager) { + throw new IllegalArgumentException("The correlation path '" + correlationPath + "' does not seem to be part of a parent query!"); + } - String correlationParentAlias = parts[0]; - String correlationPathWithoutAlias = correlationPath.substring(parts[0].length() + 1); + String correlatedAttribute; + Expression correlatedAttributeExpr; - // We assume that this is a subquery join manager here - AliasInfo aliasInfo = aliasManager.getAliasInfo(correlationParentAlias); - if (aliasInfo == null || !(aliasInfo instanceof JoinAliasInfo) || aliasInfo.getAliasOwner() == aliasManager) { - throw new IllegalArgumentException("No join node for the alias '" + correlationParentAlias + "' could be found in a parent query!"); + if (result.hasField()) { + correlatedAttribute = result.joinFields(elementExpr.toString()); + correlatedAttributeExpr = expressionFactory.createSimpleExpression(correlatedAttribute, false); + } else { + correlatedAttribute = elementExpr.toString(); + correlatedAttributeExpr = elementExpr; } - JoinNode correlationParent = ((JoinAliasInfo) aliasInfo).getJoinNode(); - Expression correlationPathWithoutAliasExpression = expressionFactory.createPathExpression(correlationPathWithoutAlias); - AttributeHolder joinResult = JpaUtils.getAttributeForJoining(metamodel, correlationParent.getPropertyClass(), correlationParent.getParentTreatType(), correlationPathWithoutAliasExpression, null); + AttributeHolder joinResult = JpaUtils.getAttributeForJoining(metamodel, correlationParent.getType(), correlatedAttributeExpr, null); Class attributeType = joinResult.getAttributeJavaType(); if (rootAlias == null) { @@ -588,10 +637,10 @@ String addRoot(String correlationPath, String rootAlias) { rootAlias = aliasManager.generatePostfixedAlias(alias); } } - // TODO: Implement treat support for correlated subqueries - String treatType = null; + + ManagedType managedType = metamodel.managedType(attributeType); JoinAliasInfo rootAliasInfo = new JoinAliasInfo(rootAlias, rootAlias, true, true, aliasManager); - JoinNode rootNode = new JoinNode(correlationParent, correlationPathWithoutAlias, treatType, rootAliasInfo, attributeType, null, null); + JoinNode rootNode = JoinNode.createCorrelationRootNode(correlationParent, correlatedAttribute, managedType, treatEntityType, rootAliasInfo); rootAliasInfo.setJoinNode(rootNode); rootNodes.add(rootNode); // register root alias in aliasManager @@ -661,6 +710,13 @@ boolean hasJoins() { if (!n.getNodes().isEmpty() || !n.getEntityJoinNodes().isEmpty()) { return true; } + if (!n.getTreatedJoinNodes().isEmpty()) { + for (JoinNode treatedNode : n.getTreatedJoinNodes().values()) { + if (!treatedNode.getNodes().isEmpty() || !treatedNode.getEntityJoinNodes().isEmpty()) { + return true; + } + } + } } return false; @@ -686,17 +742,6 @@ Set getEntityFunctionNodes() { return entityFunctionNodes; } - private void fillCollectionJoinsNodesRec(JoinNode node, Set collectionNodes) { - for (JoinTreeNode treeNode : node.getNodes().values()) { - if (treeNode.isCollection()) { - collectionNodes.addAll(treeNode.getJoinNodes().values()); - for (JoinNode childNode : treeNode.getJoinNodes().values()) { - fillCollectionJoinsNodesRec(childNode, collectionNodes); - } - } - } - } - public JoinManager getParent() { return parent; } @@ -724,9 +769,9 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St JoinNode correlationParent = rootNode.getCorrelationParent(); if (externalRepresenation && rootNode.getValueCount() > 0) { - ManagedType type = metamodel.getManagedType(rootNode.getPropertyClass()); + ManagedType type = rootNode.getManagedType(); final int attributeCount = rootNode.getAttributeCount(); - if (rootNode.getPropertyClass() != ValuesEntity.class) { + if (type.getJavaType() != ValuesEntity.class) { if (type instanceof EntityType) { sb.append(((EntityType) type).getName()); } else { @@ -747,9 +792,9 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St } sb.setCharAt(sb.length() - 1, ')'); - } else if (externalRepresenation && explicitVersionEntities.get(rootNode.getPropertyClass()) != null) { - DbmsModificationState state = explicitVersionEntities.get(rootNode.getPropertyClass()).get(rootNode.getAlias()); - EntityType type = metamodel.entity(rootNode.getPropertyClass()); + } else if (externalRepresenation && explicitVersionEntities.get(rootNode.getType()) != null) { + DbmsModificationState state = explicitVersionEntities.get(rootNode.getType()).get(rootNode.getAlias()); + EntityType type = rootNode.getEntityType(); if (state == DbmsModificationState.NEW) { sb.append("NEW("); } else { @@ -759,11 +804,9 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St sb.append(')'); } else { if (correlationParent != null) { - sb.append(correlationParent.getAliasInfo().getAlias()); - sb.append('.'); - sb.append(rootNode.getCorrelationPath()); + renderCorrelationJoinPath(sb, correlationParent.getAliasInfo(), rootNode); } else { - EntityType type = metamodel.entity(rootNode.getPropertyClass()); + EntityType type = rootNode.getEntityType(); sb.append(type.getName()); } } @@ -780,6 +823,9 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St // TODO: not sure if needed since applyImplicitJoins will already invoke that rootNode.registerDependencies(); applyJoins(sb, rootNode.getAliasInfo(), rootNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch); + for (JoinNode treatedNode : rootNode.getTreatedJoinNodes().values()) { + applyJoins(sb, treatedNode.getAliasInfo(), treatedNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch); + } if (!rootNode.getEntityJoinNodes().isEmpty()) { // TODO: Fix this with #216 boolean isCollection = true; @@ -795,7 +841,7 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St sb.append(", "); - EntityType type = metamodel.entity(entityNode.getPropertyClass()); + EntityType type = entityNode.getEntityType(); sb.append(type.getName()); sb.append(' '); @@ -826,6 +872,9 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St renderedJoins.add(entityNode); applyJoins(sb, entityNode.getAliasInfo(), entityNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch); + for (JoinNode treatedNode : entityNode.getTreatedJoinNodes().values()) { + applyJoins(sb, treatedNode.getAliasInfo(), treatedNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch); + } } } } @@ -878,6 +927,11 @@ private void renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNode n renderedJoins.add(node); return; } + // We only render treat joins, but not treated join nodes. These treats are just "optional casts" that don't affect joining + if (node.isTreatedJoinNode()) { + renderedJoins.add(node); + return; + } switch (node.getJoinType()) { case INNER: sb.append(" JOIN "); @@ -900,39 +954,8 @@ private void renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNode n sb.append(aliasPrefix); } - if (node.getTreatType() != null) { - if (mainQuery.jpaProvider.supportsTreatJoin()) { - sb.append("TREAT("); - renderParentAlias(sb, node, joinBase.getAlias()); - sb.append(node.getParentTreeNode().getRelationName()); - sb.append(" AS "); - sb.append(node.getTreatType()); - sb.append(") "); - } else if (mainQuery.jpaProvider.supportsSubtypePropertyResolving()) { - sb.append(joinBase.getAlias()).append('.').append(node.getParentTreeNode().getRelationName()).append(' '); - } else { - throw new IllegalArgumentException("Treat should not be used as the JPA provider does not support subtype property access!"); - } - } else if (node.getAliasInfo().isRootNode()) { - sb.append(metamodel.entity(node.getPropertyClass()).getName()).append(' '); - } else if (node.isQualifiedJoin()) { - sb.append(node.getQualificationExpression()); - sb.append('('); - sb.append(joinBase.getJoinNode().getAlias()); - sb.append(')'); - sb.append(' '); - } else { - if (joinBase.getJoinNode().isQualifiedJoin()) { - sb.append(joinBase.getJoinNode().getQualificationExpression()); - sb.append('('); - sb.append(joinBase.getJoinNode().getParent().getAlias()); - sb.append(')'); - sb.append('.'); - } else { - renderParentAlias(sb, node, joinBase.getAlias()); - } - sb.append(node.getParentTreeNode().getRelationName()).append(' '); - } + renderJoinPath(sb, joinBase, node); + sb.append(' '); if (aliasPrefix != null) { sb.append(aliasPrefix); @@ -958,21 +981,78 @@ private void renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNode n } } - private void renderParentAlias(StringBuilder sb, JoinNode parentNode, String alias) { - if (parentNode.getParentTreatType() != null) { - if (mainQuery.jpaProvider.supportsRootTreatJoin()) { + private void renderCorrelationJoinPath(StringBuilder sb, JoinAliasInfo joinBase, JoinNode node) { + if (node.getTreatType() != null) { + if (mainQuery.jpaProvider.supportsTreatCorrelation()) { sb.append("TREAT("); - sb.append(alias); + renderAlias(sb, joinBase.getJoinNode(), mainQuery.jpaProvider.supportsRootTreat()); + sb.append('.'); + sb.append(node.getCorrelationPath()); sb.append(" AS "); - sb.append(parentNode.getParentTreatType()); - sb.append(")."); - } else if (mainQuery.jpaProvider.supportsSubtypePropertyResolving()) { - sb.append(alias).append('.'); + sb.append(node.getTreatType().getName()); + sb.append(')'); + } else if (mainQuery.jpaProvider.supportsSubtypeRelationResolving()) { + sb.append(joinBase.getAlias()).append('.').append(node.getCorrelationPath()); } else { throw new IllegalArgumentException("Treat should not be used as the JPA provider does not support subtype property access!"); } } else { - sb.append(alias).append('.'); + JoinNode baseNode = joinBase.getJoinNode(); + if (baseNode.getTreatType() != null) { + if (mainQuery.jpaProvider.supportsRootTreatJoin()) { + baseNode.appendAlias(sb, true); + } else if (mainQuery.jpaProvider.supportsSubtypeRelationResolving()) { + baseNode.appendAlias(sb, false); + } else { + throw new IllegalArgumentException("Treat should not be used as the JPA provider does not support subtype property access!"); + } + } else { + baseNode.appendAlias(sb, false); + } + + sb.append('.').append(node.getCorrelationPath()); + } + } + + private void renderJoinPath(StringBuilder sb, JoinAliasInfo joinBase, JoinNode node) { + if (node.getTreatType() != null) { + if (mainQuery.jpaProvider.supportsTreatJoin()) { + sb.append("TREAT("); + renderAlias(sb, joinBase.getJoinNode(), mainQuery.jpaProvider.supportsRootTreat()); + sb.append('.'); + sb.append(node.getParentTreeNode().getRelationName()); + sb.append(" AS "); + sb.append(node.getTreatType().getName()); + sb.append(')'); + } else if (mainQuery.jpaProvider.supportsSubtypeRelationResolving()) { + sb.append(joinBase.getAlias()).append('.').append(node.getParentTreeNode().getRelationName()); + } else { + throw new IllegalArgumentException("Treat should not be used as the JPA provider does not support subtype property access!"); + } + } else if (node.getCorrelationPath() == null && node.getAliasInfo().isRootNode()) { + sb.append(node.getEntityType().getName()); + } else if (node.isQualifiedJoin()) { + sb.append(node.getQualificationExpression()); + sb.append('('); + sb.append(joinBase.getJoinNode().getAlias()); + sb.append(')'); + } else { + renderAlias(sb, joinBase.getJoinNode(), mainQuery.jpaProvider.supportsRootTreatJoin()); + sb.append('.').append(node.getParentTreeNode().getRelationName()); + } + } + + private void renderAlias(StringBuilder sb, JoinNode baseNode, boolean supportsTreat) { + if (baseNode.getTreatType() != null) { + if (supportsTreat) { + baseNode.appendAlias(sb, true); + } else if (mainQuery.jpaProvider.supportsSubtypeRelationResolving()) { + baseNode.appendAlias(sb, false); + } else { + throw new IllegalArgumentException("Treat should not be used as the JPA provider does not support subtype property access!"); + } + } else { + baseNode.appendAlias(sb, false); } } @@ -1017,6 +1097,7 @@ private void applyJoins(StringBuilder sb, JoinAliasInfo joinBase, List } stack.addAll(node.getEntityJoinNodes()); + stack.addAll(node.getTreatedJoinNodes().values()); // We have to render any dependencies this join node has before actually rendering itself if (!node.getDependencies().isEmpty()) { @@ -1191,7 +1272,7 @@ JoinOnBuilder joinOn(X result, String base, Class clazz, String alias, } JoinAliasInfo joinAliasInfo = new JoinAliasInfo(alias, null, false, true, aliasManager); - JoinNode entityJoinNode = new JoinNode(baseNode, null, null, joinAliasInfo, type, entityType.getJavaType(), null, null); + JoinNode entityJoinNode = JoinNode.createEntityJoinNode(baseNode, type, entityType, joinAliasInfo); joinAliasInfo.setJoinNode(entityJoinNode); baseNode.addEntityJoin(entityJoinNode); aliasManager.registerAliasInfo(joinAliasInfo); @@ -1208,6 +1289,7 @@ 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.createJoinPathExpression(path); PathElementExpression elementExpr; + String treatType = null; JoinResult result; JoinNode current; if (expr instanceof PathExpression) { @@ -1219,7 +1301,7 @@ JoinNode join(String path, String alias, JoinType type, boolean fetch, boolean d List pathElements = pathExpression.getExpressions(); elementExpr = pathElements.get(pathElements.size() - 1); - result = implicitJoin(null, null, pathExpression, null, 0, pathElements.size() - 1); + result = implicitJoin(null, pathExpression, null, 0, pathElements.size() - 1, false); current = result.baseNode; } else if (expr instanceof TreatExpression) { TreatExpression treatExpression = (TreatExpression) expr; @@ -1234,8 +1316,9 @@ JoinNode join(String path, String alias, JoinType type, boolean fetch, boolean d PathExpression pathExpression = (PathExpression) expression; List pathElements = pathExpression.getExpressions(); elementExpr = pathElements.get(pathElements.size() - 1); - result = implicitJoin(null, null, pathExpression, null, 0, pathElements.size() - 1); + result = implicitJoin(null, pathExpression, null, 0, pathElements.size() - 1, false); current = result.baseNode; + treatType = treatExpression.getType(); } else { throw new IllegalArgumentException("Unexpected expression type[" + expression.getClass().getSimpleName() + "] in treat expression: " + treatExpression); } @@ -1245,25 +1328,18 @@ JoinNode join(String path, String alias, JoinType type, boolean fetch, boolean d if (elementExpr instanceof ArrayExpression) { throw new IllegalArgumentException("Array expressions are not allowed!"); + } else if (elementExpr instanceof MapKeyExpression) { + MapKeyExpression mapKeyExpression = (MapKeyExpression) elementExpr; + boolean fromSubquery = false; + boolean fromSelectAlias = false; + boolean joinRequired = true; + current = joinMapKey(mapKeyExpression, alias, null, fromSubquery, fromSelectAlias, joinRequired, fetch, false, defaultJoin); + result = new JoinResult(current, null, current.getType()); } else { - String treatType = null; - if (expr instanceof TreatExpression) { - treatType = ((TreatExpression) expr).getType(); - } - - if (elementExpr instanceof MapKeyExpression) { - MapKeyExpression mapKeyExpression = (MapKeyExpression) elementExpr; - boolean fromSubquery = false; - boolean fromSelectAlias = false; - boolean joinRequired = true; - current = joinMapKey(mapKeyExpression, alias, null, fromSubquery, fromSelectAlias, joinRequired, fetch, false, defaultJoin); - result = new JoinResult(current, null, current.getPropertyClass()); - } else { - List joinRelationAttributes = result.addToList(new ArrayList()); - joinRelationAttributes.add(elementExpr.toString()); - current = current == null ? getRootNodeOrFail("Could not join path [", path, "] because it did not use an absolute path but multiple root nodes are available!") : current; - result = createOrUpdateNode(current, result.typeName, joinRelationAttributes, treatType, alias, type, false, defaultJoin); - } + List joinRelationAttributes = result.addToList(new ArrayList()); + joinRelationAttributes.add(elementExpr.toString()); + current = current == null ? getRootNodeOrFail("Could not join path [", path, "] because it did not use an absolute path but multiple root nodes are available!") : current; + result = createOrUpdateNode(current, joinRelationAttributes, treatType, alias, type, false, defaultJoin); } if (fetch) { @@ -1312,7 +1388,6 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin PathElementExpression elementExpr = pathElements.get(pathElements.size() - 1); boolean singleValuedAssociationIdExpression = false; JoinNode current = null; - String currentTreatType = null; List resultFields = new ArrayList(); JoinResult currentResult; @@ -1328,9 +1403,8 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin if (pathElements.size() > startIndex + 1) { int maybeSingularAssociationIndex = pathElements.size() - 2; int maybeSingularAssociationIdIndex = pathElements.size() - 1; - currentResult = implicitJoin(current, currentTreatType, pathExpression, fromClause, startIndex, maybeSingularAssociationIndex); + currentResult = implicitJoin(current, pathExpression, fromClause, startIndex, maybeSingularAssociationIndex, false); current = currentResult.baseNode; - currentTreatType = currentResult.typeName; resultFields = currentResult.addToList(resultFields); singleValuedAssociationIdExpression = isSingleValuedAssociationId(currentResult, pathElements, idRemovable); @@ -1346,14 +1420,12 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin AliasInfo a = aliasManager.getAliasInfo(pathElements.get(maybeSingularAssociationIndex).toString()); // We know this can only be a join node alias current = ((JoinAliasInfo) a).getJoinNode(); - currentTreatType = null; resultFields = Collections.emptyList(); } } else { // Need a normal join - currentResult = implicitJoin(current, currentTreatType, pathExpression, fromClause, maybeSingularAssociationIndex, pathElements.size() - 1); + currentResult = implicitJoin(current, pathExpression, fromClause, maybeSingularAssociationIndex, pathElements.size() - 1, false); current = currentResult.baseNode; - currentTreatType = currentResult.typeName; resultFields = currentResult.addToList(resultFields); singleValuedAssociationIdExpression = false; } @@ -1362,29 +1434,27 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin if (currentResult.hasField()) { // currentResult.typeName? // Redo the joins for embeddables by moving the start index back - currentResult = implicitJoin(current, currentTreatType, pathExpression, fromClause, maybeSingularAssociationIndex - currentResult.fields.size(), maybeSingularAssociationIdIndex); + currentResult = implicitJoin(current, pathExpression, fromClause, maybeSingularAssociationIndex - currentResult.fields.size(), maybeSingularAssociationIdIndex, false); if (currentResult.fields != resultFields) { resultFields.clear(); } } else { - currentResult = implicitJoin(current, currentTreatType, pathExpression, fromClause, maybeSingularAssociationIndex, maybeSingularAssociationIdIndex); + currentResult = implicitJoin(current, pathExpression, fromClause, maybeSingularAssociationIndex, maybeSingularAssociationIdIndex, false); } current = currentResult.baseNode; - currentTreatType = currentResult.typeName; resultFields = currentResult.addToList(resultFields); } } else { // Single element expression like "alias", "relation", "property" or "alias.relation" - currentResult = implicitJoin(current, currentTreatType, pathExpression, fromClause, startIndex, pathElements.size() - 1); + currentResult = implicitJoin(current, pathExpression, fromClause, startIndex, pathElements.size() - 1, false); current = currentResult.baseNode; - currentTreatType = currentResult.typeName; resultFields = currentResult.addToList(resultFields); if (idRemovable) { if (current != null) { // If there is a "base node" i.e. a current, the expression has 2 elements - if (isId(current.getPropertyClass(), elementExpr)) { + if (isId(current.getType(), elementExpr)) { // We remove the "id" part elementExpr = null; // Treat it like a single valued association id expression @@ -1403,7 +1473,7 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin if (a == null) { // If the element expression is an alias, there is nothing to replace current = getRootNodeOrFail("Could not join path [", expression, "] because it did not use an absolute path but multiple root nodes are available!"); - if (isId(current.getPropertyClass(), elementExpr)) { + if (isId(current.getType(), elementExpr)) { // We replace the "id" part with the alias elementExpr = new PropertyExpression(current.getAlias()); } @@ -1444,10 +1514,10 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin if (targetTypeName != null) { // Treated root path ManagedType targetType = metamodel.managedType(targetTypeName); - result = new JoinResult(pathJoinNode, null, targetTypeName, targetType.getJavaType()); + result = new JoinResult(pathJoinNode, null, targetType.getJavaType()); } else { // Naked join alias usage like in "KEY(joinAlias)" - result = new JoinResult(pathJoinNode, null, pathJoinNode.getPropertyClass()); + result = new JoinResult(pathJoinNode, null, pathJoinNode.getType()); } } } else if (pathElements.size() == 1 && elementExpr instanceof QualifiedExpression) { @@ -1466,7 +1536,7 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin throw new IllegalArgumentException("Unknown qualified expression type: " + elementExpr); } - result = new JoinResult(baseNode, null, baseNode.getPropertyClass()); + result = new JoinResult(baseNode, null, baseNode.getType()); } else { // current might be null if (current == null) { @@ -1489,15 +1559,14 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin if (elementExpr != null) { AttributeHolder attributeHolder = JpaUtils.getAttributeForJoining( metamodel, - singleValuedAssociationRoot.getPropertyClass(), - singleValuedAssociationRoot.getTreatType(), + singleValuedAssociationRoot.getType(), elementExpr, singleValuedAssociationRoot.getAlias() ); Class type = attributeHolder.getAttributeJavaType(); result = new JoinResult(singleValuedAssociationRoot, Arrays.asList(elementExpr.toString()), type); } else { - result = new JoinResult(singleValuedAssociationRoot, null, singleValuedAssociationRoot.getPropertyClass()); + result = new JoinResult(singleValuedAssociationRoot, null, singleValuedAssociationRoot.getType()); } } else { treeNode = current.getNodes().get(associationName); @@ -1506,15 +1575,14 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin if (elementExpr != null) { AttributeHolder attributeHolder = JpaUtils.getAttributeForJoining( metamodel, - treeNode.getDefaultNode().getPropertyClass(), - treeNode.getDefaultNode().getTreatType(), + treeNode.getDefaultNode().getType(), elementExpr, treeNode.getDefaultNode().getAlias() ); Class type = attributeHolder.getAttributeJavaType(); result = new JoinResult(treeNode.getDefaultNode(), Arrays.asList(elementExpr.toString()), type); } else { - result = new JoinResult(treeNode.getDefaultNode(), null, treeNode.getDefaultNode().getPropertyClass()); + result = new JoinResult(treeNode.getDefaultNode(), null, treeNode.getDefaultNode().getType()); } } else { if (elementExpr != null) { @@ -1522,8 +1590,7 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin Expression resultExpr = expressionFactory.createSimpleExpression(associationName + '.' + elementString, false); AttributeHolder attributeHolder = JpaUtils.getAttributeForJoining( metamodel, - current.getPropertyClass(), - current.getTreatType(), + current.getType(), resultExpr, current.getAlias() ); @@ -1533,8 +1600,7 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin Expression resultExpr = expressionFactory.createSimpleExpression(associationName, false); AttributeHolder attributeHolder = JpaUtils.getAttributeForJoining( metamodel, - current.getPropertyClass(), - current.getTreatType(), + current.getType(), resultExpr, current.getAlias() ); @@ -1568,7 +1634,7 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin current = matchingNode; } else { String joinAlias = getJoinAlias(arrayExpr); - currentResult = createOrUpdateNode(current, currentTreatType, Arrays.asList(joinRelationName), null, joinAlias, null, true, false); + currentResult = createOrUpdateNode(current, Arrays.asList(joinRelationName), null, joinAlias, null, true, false); current = currentResult.baseNode; // TODO: Not sure if necessary if (currentResult.hasField()) { @@ -1578,29 +1644,29 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin generateAndApplyOnPredicate(current, arrayExpr); } - result = new JoinResult(current, null, current.getPropertyClass()); + result = new JoinResult(current, null, current.getType()); } else if (!pathExpression.isUsedInCollectionFunction()) { if (resultFields.isEmpty()) { - result = implicitJoinSingle(current, currentTreatType, elementExpr.toString(), objectLeafAllowed, joinRequired); + result = implicitJoinSingle(current, elementExpr.toString(), objectLeafAllowed, joinRequired); } else { resultFields.add(elementExpr.toString()); String attributeName = StringUtils.join(".", resultFields); // Validates and gets the path type - getPathType(current.getPropertyClass(), currentTreatType, attributeName, pathExpression); + getPathType(current.getType(), attributeName, pathExpression); - result = implicitJoinSingle(current, currentTreatType, attributeName, objectLeafAllowed, joinRequired); + result = implicitJoinSingle(current, attributeName, objectLeafAllowed, joinRequired); } } else { if (resultFields.isEmpty()) { String attributeName = elementExpr.toString(); - Class type = getPathType(current.getPropertyClass(), currentTreatType, attributeName, pathExpression); + Class type = getPathType(current.getType(), attributeName, pathExpression); result = new JoinResult(current, Arrays.asList(attributeName), type); } else { resultFields.add(elementExpr.toString()); String attributeName = StringUtils.join(".", resultFields); - Class type = getPathType(current.getPropertyClass(), currentTreatType, attributeName, pathExpression); + Class type = getPathType(current.getType(), attributeName, pathExpression); result = new JoinResult(current, resultFields, type); } @@ -1617,13 +1683,9 @@ public void implicitJoin(Expression expression, boolean objectLeafAllowed, Strin } if (result.isLazy()) { - pathExpression.setPathReference(new LazyPathReference(result.baseNode, result.joinFields(), result.typeName, result.type)); + pathExpression.setPathReference(new LazyPathReference(result.baseNode, result.joinFields(), result.type)); } else { - pathExpression.setPathReference(new SimplePathReference(result.baseNode, result.joinFields(), result.typeName, result.type)); - } - - if (result.hasTreatedSubpath) { - pathExpression.setHasTreatedSubpath(true); + pathExpression.setPathReference(new SimplePathReference(result.baseNode, result.joinFields(), result.type)); } } else if (expression instanceof FunctionExpression) { List expressions = ((FunctionExpression) expression).getExpressions(); @@ -1655,13 +1717,11 @@ private JoinNode getFetchOwner(JoinNode node) { private static class LazyPathReference implements PathReference { private final JoinNode baseNode; private final String field; - private final String typeName; private final Class type; - public LazyPathReference(JoinNode baseNode, String field, String typeName, Class type) { + public LazyPathReference(JoinNode baseNode, String field, Class type) { this.baseNode = baseNode; this.field = field; - this.typeName = typeName; this.type = type; } @@ -1685,11 +1745,6 @@ public String getField() { return field; } - @Override - public String getTreatTypeName() { - return typeName; - } - @Override public Class getType() { return type; @@ -1701,7 +1756,6 @@ public int hashCode() { 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; } @@ -1731,20 +1785,13 @@ public boolean equals(Object obj) { } 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 Class getPathType(Class baseType, String treatTypeName, String expression, PathExpression pathExpression) { + private Class getPathType(Class baseType, String expression, PathExpression pathExpression) { try { - return JpaUtils.getAttributeForJoining(metamodel, baseType, treatTypeName, expressionFactory.createPathExpression(expression), null).getAttributeJavaType(); + return JpaUtils.getAttributeForJoining(metamodel, baseType, expressionFactory.createPathExpression(expression), null).getAttributeJavaType(); } catch (IllegalArgumentException ex) { throw new IllegalArgumentException("The join path [" + pathExpression + "] has a non joinable part [" + expression + "]"); @@ -1753,50 +1800,52 @@ private Class getPathType(Class baseType, String treatTypeName, String exp private boolean isSingleValuedAssociationId(JoinResult joinResult, List pathElements, boolean idRemovable) { JoinNode parent = joinResult.baseNode; - String parentTypeName = joinResult.typeName; int maybeSingularAssociationIndex = pathElements.size() - 2; int maybeSingularAssociationIdIndex = pathElements.size() - 1; - ManagedType baseType; + Type baseType; AttributeHolder maybeSingularAssociationJoinResult; PathElementExpression maybeSingularAssociationNameExpression = pathElements.get(maybeSingularAssociationIndex); String maybeSingularAssociationName = getSimpleName(maybeSingularAssociationNameExpression); if (parent == null) { // This is the case when we have exactly 2 path elements - AliasInfo a = aliasManager.getAliasInfo(maybeSingularAssociationName); - - if (a == null) { - // if the path element is no alias we can do some optimizations - parent = getRootNodeOrFail("Ambiguous join path [", maybeSingularAssociationName, "] because of multiple root nodes!"); - baseType = metamodel.managedType(parent.getPropertyClass()); - maybeSingularAssociationJoinResult = JpaUtils.getAttributeForJoining(metamodel, baseType.getJavaType(), joinResult.typeName, maybeSingularAssociationNameExpression, parent.getAlias()); - } else if (!(a instanceof JoinAliasInfo)) { - throw new IllegalArgumentException("Can't dereference select alias in the expression!"); + if (maybeSingularAssociationNameExpression instanceof TreatExpression) { + // When we dereference a treat expression, we simply say this can never be a single valued association id + return false; } else { - // If there is a JoinAliasInfo for the path element, we have to use the alias - // We can only "consider" this path a single valued association id when we are about to "remove" the id part - if (idRemovable) { - Class maybeSingularAssociationClass = ((JoinAliasInfo) a).getJoinNode().getPropertyClass(); - PathElementExpression maybeSingularAssociationIdExpression = pathElements.get(maybeSingularAssociationIdIndex); - - return isId(maybeSingularAssociationClass, maybeSingularAssociationIdExpression); + AliasInfo a = aliasManager.getAliasInfo(maybeSingularAssociationName); + + if (a == null) { + // if the path element is no alias we can do some optimizations + parent = getRootNodeOrFail("Ambiguous join path [", maybeSingularAssociationName, "] because of multiple root nodes!"); + baseType = parent.getManagedType(); + maybeSingularAssociationJoinResult = JpaUtils.getAttributeForJoining(metamodel, baseType.getJavaType(), maybeSingularAssociationNameExpression, parent.getAlias()); + } else if (!(a instanceof JoinAliasInfo)) { + throw new IllegalArgumentException("Can't dereference select alias in the expression!"); } else { - // Otherwise we return false in order to signal that a normal implicit join should be done - return false; + // If there is a JoinAliasInfo for the path element, we have to use the alias + // We can only "consider" this path a single valued association id when we are about to "remove" the id part + if (idRemovable) { + Class maybeSingularAssociationClass = ((JoinAliasInfo) a).getJoinNode().getType(); + PathElementExpression maybeSingularAssociationIdExpression = pathElements.get(maybeSingularAssociationIdIndex); + + return isId(maybeSingularAssociationClass, maybeSingularAssociationIdExpression); + } else { + // Otherwise we return false in order to signal that a normal implicit join should be done + return false; + } } } } else { - Class parentClass = parent.getPropertyClass(); - if (joinResult.hasField()) { Expression fieldExpression = expressionFactory.createPathExpression(joinResult.joinFields()); - AttributeHolder result = JpaUtils.getAttributeForJoining(metamodel, parentClass, parentTypeName, fieldExpression, parent.getAlias()); - baseType = metamodel.getManagedType(result.getAttributeJavaType()); + AttributeHolder result = JpaUtils.getAttributeForJoining(metamodel, parent.getType(), fieldExpression, parent.getAlias()); + baseType = metamodel.type(result.getAttributeJavaType()); } else { - baseType = JpaUtils.getManagedType(metamodel, parentClass, parentTypeName); + baseType = parent.getNodeType(); } - maybeSingularAssociationJoinResult = JpaUtils.getAttributeForJoining(metamodel, baseType.getJavaType(), null, maybeSingularAssociationNameExpression, null); + maybeSingularAssociationJoinResult = JpaUtils.getAttributeForJoining(metamodel, baseType.getJavaType(), maybeSingularAssociationNameExpression, null); } Attribute maybeSingularAssociation = maybeSingularAssociationJoinResult.getAttribute(); @@ -1817,12 +1866,12 @@ private boolean isSingleValuedAssociationId(JoinResult joinResult, List) { // Get the base type. This is important if the path is "deeper" i.e. when having embeddables - baseType = JpaUtils.getManagedType(metamodel, parent.getPropertyClass(), parentTypeName); + baseType = parent.getNodeType(); String attributePath = joinResult.joinFields(maybeSingularAssociationName); - if (mainQuery.jpaProvider.isForeignJoinColumn(baseType, attributePath)) { + if (mainQuery.jpaProvider.isForeignJoinColumn((ManagedType) baseType, attributePath)) { return false; } - } else if (mainQuery.jpaProvider.isForeignJoinColumn(baseType, maybeSingularAssociation.getName())) { + } else if (mainQuery.jpaProvider.isForeignJoinColumn((ManagedType) baseType, maybeSingularAssociation.getName())) { return false; } @@ -1890,7 +1939,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, null, joinNode.getPropertyClass())); + keyPath.setPathReference(new SimplePathReference(joinNode, null, joinNode.getType())); Attribute arrayBaseAttribute = joinNode.getParentTreeNode().getAttribute(); Expression keyExpression; if (arrayBaseAttribute instanceof ListAttribute) { @@ -1934,10 +1983,9 @@ private void generateAndApplyOnPredicate(JoinNode joinNode, ArrayExpression arra } } - private JoinResult implicitJoin(JoinNode current, String currentTreatType, PathExpression pathExpression, ClauseType fromClause, int start, int end) { + private JoinResult implicitJoin(JoinNode current, PathExpression pathExpression, ClauseType fromClause, int start, int end, boolean allowParentAliases) { List pathElements = pathExpression.getExpressions(); List resultFields = new ArrayList(); - String currentTargetType = currentTreatType; PathElementExpression elementExpr; for (int i = start; i < end; i++) { @@ -1974,14 +2022,11 @@ private JoinResult implicitJoin(JoinNode current, String currentTreatType, PathE generateAndApplyOnPredicate(current, arrayExpr); } else { String joinAlias = getJoinAlias(arrayExpr); - final JoinResult result = createOrUpdateNode(current, currentTargetType, joinRelationAttributes, null, joinAlias, null, true, false); + final JoinResult result = createOrUpdateNode(current, joinRelationAttributes, null, joinAlias, null, true, false); current = result.baseNode; resultFields = result.addToList(resultFields); 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!"); @@ -1992,14 +2037,12 @@ private JoinResult implicitJoin(JoinNode current, String currentTreatType, PathE 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(), fromClause, fromSubquery, fromSelectAlias, true, false, fetch); JoinNode treatedJoinNode = (JoinNode) treatedPathExpression.getBaseNode(); - current = treatedJoinNode; - // TODO: check if the types are actually assignable - currentTargetType = treatExpression.getType(); + EntityType treatType = metamodel.getEntity(treatExpression.getType()); + current = treatedJoinNode.getTreatedJoinNode(treatType); } else { throw new UnsupportedOperationException("Unsupported treated expression type: " + treatExpression.getExpression().getClass()); } @@ -2010,8 +2053,6 @@ private JoinResult implicitJoin(JoinNode current, String currentTreatType, PathE boolean joinRequired = true; boolean fetch = false; current = joinMapKey(mapKeyExpression, null, fromClause, fromSubquery, fromSelectAlias, joinRequired, fetch, true, true); - // Reset target type - currentTargetType = null; } else if (elementExpr instanceof MapValueExpression) { MapValueExpression mapValueExpression = (MapValueExpression) elementExpr; boolean fromSubquery = false; @@ -2021,8 +2062,6 @@ private JoinResult implicitJoin(JoinNode current, String currentTreatType, PathE implicitJoin(mapValueExpression.getPath(), true, null, fromClause, fromSubquery, fromSelectAlias, joinRequired, fetch); current = (JoinNode) mapValueExpression.getPath().getBaseNode(); - // Reset target type - currentTargetType = null; } else if (pathElements.size() == 1 && (aliasInfo = aliasManager.getAliasInfoForBottomLevel(elementExpr.toString())) != null) { if (aliasInfo instanceof SelectInfo) { throw new IllegalArgumentException("Can't dereference a select alias"); @@ -2030,24 +2069,18 @@ private JoinResult implicitJoin(JoinNode current, String currentTreatType, PathE // 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, currentTargetType, resultFields, null, null, null, true, true); + JoinResult currentResult = createOrUpdateNode(current, resultFields, null, null, null, true, true); current = currentResult.baseNode; if (!currentResult.hasField()) { resultFields.clear(); } - // Reset target type - currentTargetType = null; } else { - final JoinResult result = implicitJoinSingle(current, currentTargetType, elementExpr.toString()); + final JoinResult result = implicitJoinSingle(current, elementExpr.toString(), allowParentAliases); if (current != result.baseNode) { current = result.baseNode; - // Reset target type - currentTargetType = null; } resultFields = result.addToList(resultFields); } @@ -2056,7 +2089,7 @@ private JoinResult implicitJoin(JoinNode current, String currentTreatType, PathE } if (resultFields.isEmpty()) { - return new JoinResult(current, null, currentTargetType, current == null ? null : current.getPropertyClass()); + return new JoinResult(current, null, current == null ? null : current.getType()); } else { StringBuilder sb = new StringBuilder(); sb.append(resultFields.get(0)); @@ -2065,8 +2098,8 @@ private JoinResult implicitJoin(JoinNode current, String currentTreatType, PathE sb.append(resultFields.get(i)); } Expression expression = expressionFactory.createSimpleExpression(sb.toString(), false); - Class type = JpaUtils.getAttributeForJoining(metamodel, current.getPropertyClass(), currentTargetType, expression, current.getAlias()).getAttributeJavaType(); - return new JoinResult(current, resultFields, currentTargetType, type); + Class type = JpaUtils.getAttributeForJoining(metamodel, current.getType(), expression, current.getAlias()).getAttributeJavaType(); + return new JoinResult(current, resultFields, type); } } @@ -2075,10 +2108,10 @@ private JoinNode joinMapKey(MapKeyExpression mapKeyExpression, String alias, Cla JoinNode current = (JoinNode) mapKeyExpression.getPath().getBaseNode(); String joinRelationName = "KEY(" + current.getParentTreeNode().getRelationName() + ")"; MapAttribute mapAttribute = (MapAttribute) current.getParentTreeNode().getAttribute(); - Class keyType = mapAttribute.getKeyJavaType(); Attribute keyAttribute = new MapKeyAttribute<>(mapAttribute); String aliasToUse = alias == null ? current.getParentTreeNode().getRelationName() + "_key" : alias; - current = getOrCreate(current, null, joinRelationName, keyType, null, aliasToUse, JoinType.LEFT, "Ambiguous implicit join", implicit, true, keyAttribute); + Type joinRelationType = metamodel.type(mapAttribute.getKeyJavaType()); + current = getOrCreate(current, joinRelationName, joinRelationType, null, aliasToUse, JoinType.LEFT, "Ambiguous implicit join", implicit, true, keyAttribute); return current; } @@ -2087,10 +2120,10 @@ private JoinNode joinMapEntry(MapEntryExpression mapEntryExpression, String alia JoinNode current = (JoinNode) mapEntryExpression.getPath().getBaseNode(); String joinRelationName = "ENTRY(" + current.getParentTreeNode().getRelationName() + ")"; MapAttribute mapAttribute = (MapAttribute) current.getParentTreeNode().getAttribute(); - Class keyType = mapAttribute.getKeyJavaType(); Attribute entryAttribute = new MapEntryAttribute<>(mapAttribute); - String aliasToUse = alias == null ? current.getParentTreeNode().getRelationName() + "_key" : alias; - current = getOrCreate(current, null, joinRelationName, keyType, null, aliasToUse, JoinType.LEFT, "Ambiguous implicit join", implicit, true, entryAttribute); + String aliasToUse = alias == null ? current.getParentTreeNode().getRelationName() + "_entry" : alias; + Type joinRelationType = metamodel.type(Map.Entry.class); + current = getOrCreate(current, joinRelationName, joinRelationType, null, aliasToUse, JoinType.LEFT, "Ambiguous implicit join", implicit, true, entryAttribute); return current; } @@ -2099,21 +2132,23 @@ private JoinNode joinListIndex(ListIndexExpression listIndexExpression, String a JoinNode current = (JoinNode) listIndexExpression.getPath().getBaseNode(); String joinRelationName = "INDEX(" + current.getParentTreeNode().getRelationName() + ")"; ListAttribute listAttribute = (ListAttribute) current.getParentTreeNode().getAttribute(); - Class keyType = Integer.class; Attribute indexAttribute = new ListIndexAttribute<>(listAttribute); String aliasToUse = alias == null ? current.getParentTreeNode().getRelationName() + "_index" : alias; - current = getOrCreate(current, null, joinRelationName, keyType, null, aliasToUse, JoinType.LEFT, "Ambiguous implicit join", implicit, true, indexAttribute); + Type joinRelationType = metamodel.type(Integer.class); + current = getOrCreate(current, joinRelationName, joinRelationType, null, aliasToUse, JoinType.LEFT, "Ambiguous implicit join", implicit, true, indexAttribute); return current; } - private JoinResult implicitJoinSingle(JoinNode baseNode, String baseNodeTreatType, String attributeName) { + private JoinResult implicitJoinSingle(JoinNode baseNode, String attributeName, boolean allowParentAliases) { if (baseNode == null) { // When no base is given, check if the attribute name is an alias - AliasInfo aliasInfo = aliasManager.getAliasInfoForBottomLevel(attributeName); + AliasInfo aliasInfo = allowParentAliases ? + aliasManager.getAliasInfo(attributeName) : + aliasManager.getAliasInfoForBottomLevel(attributeName); if (aliasInfo != null && aliasInfo instanceof JoinAliasInfo) { JoinNode node = ((JoinAliasInfo) aliasInfo).getJoinNode(); // if it is, we can just return the join node - return new JoinResult(node, null, node.getPropertyClass()); + return new JoinResult(node, null, node.getType()); } } @@ -2123,48 +2158,32 @@ private JoinResult implicitJoinSingle(JoinNode baseNode, String baseNodeTreatTyp } // check if the path is joinable, assuming it is relative to the root (implicit root prefix) - return createOrUpdateNode(baseNode, baseNodeTreatType, Arrays.asList(attributeName), null, null, null, true, true); + return createOrUpdateNode(baseNode, Arrays.asList(attributeName), null, null, null, true, true); } - private JoinResult implicitJoinSingle(JoinNode baseNode, String treatTypeName, String attributeName, boolean objectLeafAllowed, boolean joinRequired) { + private JoinResult implicitJoinSingle(JoinNode baseNode, String attributeName, boolean objectLeafAllowed, boolean joinRequired) { JoinNode newBaseNode; String field; Class type; - boolean hasTreatedSubpath = false; boolean lazy = false; // The given path may be relative to the root or it might be an alias if (objectLeafAllowed) { - String typeName; - ManagedType baseNodeType; - - if (treatTypeName != null) { - typeName = treatTypeName; - baseNodeType = metamodel.managedType(treatTypeName); - } else { - if (baseNode.getTreatType() != null) { - typeName = baseNode.getTreatType(); - baseNodeType = metamodel.managedType(baseNode.getTreatType()); - } else { - typeName = baseNode.getPropertyClass().getSimpleName(); - baseNodeType = metamodel.managedType(baseNode.getPropertyClass()); - } - } + Type baseNodeType = baseNode.getNodeType(); AttributeHolder attributeHolder = JpaUtils.getAttributeForJoining(metamodel, baseNodeType.getJavaType(), expressionFactory.createJoinPathExpression(attributeName), baseNode.getAlias()); Attribute attr = attributeHolder.getAttribute(); if (attr == null) { - throw new IllegalArgumentException("Field with name '" + attributeName + "' was not found within managed type " + typeName); + throw new IllegalArgumentException("Field with name '" + attributeName + "' was not found within managed type " + baseNodeType.getJavaType().getName()); } if (joinRequired || attr.isCollection()) { - final JoinResult newBaseNodeResult = implicitJoinSingle(baseNode, treatTypeName, attributeName); + final JoinResult newBaseNodeResult = implicitJoinSingle(baseNode, attributeName, false); newBaseNode = newBaseNodeResult.baseNode; // check if the last path element was also joined if (newBaseNode != baseNode) { field = null; - type = newBaseNode.getPropertyClass(); + type = newBaseNode.getType(); } else { - hasTreatedSubpath = treatTypeName != null; field = attributeName; type = attributeHolder.getAttributeJavaType(); } @@ -2173,10 +2192,9 @@ private JoinResult implicitJoinSingle(JoinNode baseNode, String treatTypeName, S field = attributeName; type = attributeHolder.getAttributeJavaType(); lazy = true; - hasTreatedSubpath = treatTypeName != null; } } else { - Class baseNodeType = baseNode.getPropertyClass(); + Class baseNodeType = baseNode.getType(); AttributeHolder attributeHolder = JpaUtils.getAttributeForJoining(metamodel, baseNodeType, expressionFactory.createJoinPathExpression(attributeName), baseNode.getAlias()); Attribute attr = attributeHolder.getAttribute(); if (attr == null) { @@ -2189,7 +2207,7 @@ private JoinResult implicitJoinSingle(JoinNode baseNode, String treatTypeName, S field = attributeName; type = attr.getJavaType(); } - return new JoinResult(newBaseNode, field == null ? null : Arrays.asList(field), type, lazy, hasTreatedSubpath); + return new JoinResult(newBaseNode, field == null ? null : Arrays.asList(field), type, lazy); } private void updateClauseDependencies(JoinNode baseNode, ClauseType clauseDependency, Set seenNodes) { @@ -2223,15 +2241,8 @@ private JoinType getModelAwareType(JoinNode baseNode, Attribute attr) { } } - private JoinResult createOrUpdateNode(JoinNode baseNode, String baseNodeTreatType, List joinRelationAttributes, String treatType, String alias, JoinType joinType, boolean implicit, boolean defaultJoin) { - Class baseNodeType; - if (baseNodeTreatType != null) { - baseNodeType = metamodel.getEntity(baseNodeTreatType).getJavaType(); - } else if (baseNode.getTreatType() != null) { - baseNodeType = metamodel.getEntity(baseNode.getTreatType()).getJavaType(); - } else { - baseNodeType = baseNode.getPropertyClass(); - } + private JoinResult createOrUpdateNode(JoinNode baseNode, List joinRelationAttributes, String treatType, String alias, JoinType joinType, boolean implicit, boolean defaultJoin) { + Class baseNodeType = baseNode.getType(); String joinRelationName = StringUtils.join(".", joinRelationAttributes); AttributeHolder attrJoinResult = JpaUtils.getAttributeForJoining(metamodel, baseNodeType, expressionFactory.createJoinPathExpression(joinRelationName), baseNode.getAlias()); Attribute attr = attrJoinResult.getAttribute(); @@ -2259,18 +2270,10 @@ private JoinResult createOrUpdateNode(JoinNode baseNode, String baseNodeTreatTyp joinType = getModelAwareType(baseNode, attr); } - if (baseNodeTreatType != null) { - // Verify it's a valid type - metamodel.managedType(baseNodeTreatType).getJavaType(); - } - if (treatType != null) { - // Verify it's a valid type - metamodel.managedType(treatType).getJavaType(); - } + Type joinRelationType = metamodel.type(attrJoinResult.getAttributeJavaType()); + JoinNode newNode = getOrCreate(baseNode, joinRelationName, joinRelationType, treatType, alias, joinType, "Ambiguous implicit join", implicit, defaultJoin, attr); - JoinNode newNode = getOrCreate(baseNode, baseNodeTreatType, joinRelationName, attrJoinResult.getAttributeJavaType(), treatType, alias, joinType, "Ambiguous implicit join", implicit, defaultJoin, attr); - - return new JoinResult(newNode, null, newNode.getPropertyClass()); + return new JoinResult(newNode, null, newNode.getType()); } private void checkAliasIsAvailable(String alias, String currentJoinPath, String errorMessage) { @@ -2289,7 +2292,7 @@ private void checkAliasIsAvailable(String alias, String currentJoinPath, String } } - private JoinNode getOrCreate(JoinNode baseNode, String baseNodeTreatType, String joinRelationName, Class joinRelationClass, String treatType, String alias, JoinType type, String errorMessage, boolean implicit, boolean defaultJoin, Attribute attribute) { + private JoinNode getOrCreate(JoinNode baseNode, String joinRelationName, Type joinRelationType, String treatType, String alias, JoinType type, String errorMessage, boolean implicit, boolean defaultJoin, Attribute attribute) { JoinTreeNode treeNode = baseNode.getOrCreateTreeNode(joinRelationName, attribute); JoinNode node = treeNode.getJoinNode(alias, defaultJoin); String qualificationExpression = null; @@ -2298,7 +2301,18 @@ private JoinNode getOrCreate(JoinNode baseNode, String baseNodeTreatType, String qualificationExpression = ((QualifiedAttribute) attribute).getQualificationExpression(); } - String currentJoinPath = baseNode.getAliasInfo().getAbsolutePath() + "." + joinRelationName; + EntityType treatJoinType; + String currentJoinPath; + + if (treatType != null) { + // Verify it's a valid type + treatJoinType = metamodel.getEntity(treatType); + currentJoinPath = "TREAT(" + baseNode.getAliasInfo().getAbsolutePath() + "." + joinRelationName + " AS " + treatJoinType.getName() + ")"; + } else { + treatJoinType = null; + currentJoinPath = baseNode.getAliasInfo().getAbsolutePath() + "." + joinRelationName; + } + if (node == null) { // a join node for the join relation does not yet exist checkAliasIsAvailable(alias, currentJoinPath, errorMessage); @@ -2310,7 +2324,7 @@ private JoinNode getOrCreate(JoinNode baseNode, String baseNodeTreatType, String JoinAliasInfo newAliasInfo = new JoinAliasInfo(alias, currentJoinPath, implicit, false, aliasManager); aliasManager.registerAliasInfo(newAliasInfo); - node = new JoinNode(baseNode, treeNode, baseNodeTreatType, newAliasInfo, type, joinRelationClass, treatType, qualificationExpression); + node = JoinNode.createAssociationJoinNode(baseNode, treeNode, type, joinRelationType, treatJoinType, qualificationExpression, newAliasInfo); newAliasInfo.setJoinNode(node); treeNode.addJoinNode(node, defaultJoin); } else { @@ -2336,8 +2350,10 @@ private JoinNode getOrCreate(JoinNode baseNode, String baseNodeTreatType, String } } - if (treatType != null) { - if (!treatType.equals(node.getTreatType())) { + if (treatJoinType != null) { + if (node.getTreatType() == null) { + node = node.getTreatedJoinNode(treatJoinType); + } else if (!treatJoinType.equals(node.getTreatType())) { throw new IllegalArgumentException("A join node [" + nodeAliasInfo.getAlias() + "=" + nodeAliasInfo.getAbsolutePath() + "] " + "for treat type [" + treatType + "] conflicts with the existing treat type [" + node.getTreatType() + "]"); } @@ -2395,35 +2411,21 @@ private static class JoinResult { final JoinNode baseNode; final List fields; - final String typeName; final Class type; final boolean lazy; - final boolean hasTreatedSubpath; public JoinResult(JoinNode baseNode, List fields, Class type) { - this(baseNode, fields, null, type); - } - - public JoinResult(JoinNode baseNode, List fields, String typeName, Class type) { this.baseNode = baseNode; this.fields = fields; - this.typeName = typeName; this.type = type; this.lazy = false; - this.hasTreatedSubpath = false; - } - - public JoinResult(JoinNode baseNode, List fields, Class type, boolean lazy, boolean hasTreatedSubpath) { - this(baseNode, fields, null, type, lazy, hasTreatedSubpath); } - public JoinResult(JoinNode baseNode, List fields, String typeName, Class type, boolean lazy, boolean hasTreatedSubpath) { + public JoinResult(JoinNode baseNode, List fields, Class type, boolean lazy) { this.baseNode = baseNode; this.fields = fields; - this.typeName = typeName; this.type = type; this.lazy = lazy; - this.hasTreatedSubpath = hasTreatedSubpath; } private boolean hasField() { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java index 3b27692d40..3cde61dd67 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java @@ -37,6 +37,9 @@ import javax.persistence.Query; import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.ManagedType; +import javax.persistence.metamodel.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -56,7 +59,6 @@ */ public class JoinNode implements From, ExpressionModifier, BaseNode { - private JoinAliasInfo aliasInfo; private JoinType joinType = JoinType.LEFT; private boolean fetch = false; @@ -68,9 +70,8 @@ public class JoinNode implements From, ExpressionModifier, BaseNode { private final JoinNode correlationParent; private final String correlationPath; - private final String parentTreatType; - private final Class propertyClass; - private final String treatType; + private final Type nodeType; + private final EntityType treatType; private final String valuesFunction; private final int valueCount; private final int attributeCount; @@ -78,13 +79,15 @@ public class JoinNode implements From, ExpressionModifier, BaseNode { private final String valuesClause; private final String valuesAliases; private final String qualificationExpression; + private final JoinAliasInfo aliasInfo; + private final List joinNodesForTreatConstraint; - private final Map nodes = new TreeMap(); // Use TreeMap so that joins get applied - // alphabetically for easier testing - private final Set entityJoinNodes = new LinkedHashSet(); + private final Map nodes = new TreeMap<>(); // Use TreeMap so that joins get applied alphabetically for easier testing + private final Map treatedJoinNodes = new TreeMap<>(); + private final Set entityJoinNodes = new LinkedHashSet<>(); // contains other join nodes which this node depends on - private final Set dependencies = new HashSet(); + private final Set dependencies = new HashSet<>(); private CompoundPredicate onPredicate; @@ -92,65 +95,131 @@ public class JoinNode implements From, ExpressionModifier, BaseNode { private boolean dirty = true; private boolean cardinalityMandatory; - public JoinNode(JoinAliasInfo aliasInfo, Class propertyClass, String valuesFunction, int valueCount, int attributeCount, Query valueQuery, String valuesClause, String valuesAliases, String qualificationExpression) { - this.parent = null; - this.parentTreeNode = null; - this.parentTreatType = null; - this.aliasInfo = aliasInfo; - this.joinType = null; - this.propertyClass = propertyClass; - this.treatType = null; - this.valuesFunction = valuesFunction; - this.valueCount = valueCount; - this.attributeCount = attributeCount; - this.valueQuery = valueQuery; - this.valuesClause = valuesClause; - this.valuesAliases = valuesAliases; - this.correlationParent = null; - this.correlationPath = null; - this.qualificationExpression = qualificationExpression; - onUpdate(null); - } - - public JoinNode(JoinNode parent, JoinTreeNode parentTreeNode, String parentTreatType, JoinAliasInfo aliasInfo, JoinType joinType, Class propertyClass, String treatType, String qualificationExpression) { + private JoinNode(TreatedJoinAliasInfo treatedJoinAliasInfo) { + JoinNode treatedJoinNode = treatedJoinAliasInfo.getTreatedJoinNode(); + this.parent = treatedJoinNode.parent; + this.parentTreeNode = treatedJoinNode.parentTreeNode; + this.joinType = treatedJoinNode.joinType; + this.correlationParent = treatedJoinNode.correlationParent; + this.correlationPath = treatedJoinNode.correlationPath; + this.nodeType = treatedJoinNode.nodeType; + this.treatType = treatedJoinAliasInfo.getTreatType(); + this.qualificationExpression = treatedJoinNode.qualificationExpression; + this.valuesFunction = treatedJoinNode.valuesFunction; + this.valueCount = treatedJoinNode.valueCount; + this.attributeCount = treatedJoinNode.attributeCount; + this.valueQuery = treatedJoinNode.valueQuery; + this.valuesClause = treatedJoinNode.valuesClause; + this.valuesAliases = treatedJoinNode.valuesAliases; + this.aliasInfo = treatedJoinAliasInfo; + List joinNodesForTreatConstraint = new ArrayList<>(treatedJoinNode.joinNodesForTreatConstraint.size() + 1); + joinNodesForTreatConstraint.addAll(treatedJoinNode.joinNodesForTreatConstraint); + joinNodesForTreatConstraint.add(this); + this.joinNodesForTreatConstraint = Collections.unmodifiableList(joinNodesForTreatConstraint); + } + + private JoinNode(JoinNode parent, JoinTreeNode parentTreeNode, JoinType joinType, JoinNode correlationParent, String correlationPath, Type nodeType, EntityType treatType, String qualificationExpression, JoinAliasInfo aliasInfo) { this.parent = parent; this.parentTreeNode = parentTreeNode; - this.parentTreatType = parentTreatType; - this.aliasInfo = aliasInfo; this.joinType = joinType; - this.propertyClass = propertyClass; + this.correlationParent = correlationParent; + this.correlationPath = correlationPath; + this.nodeType = nodeType; this.treatType = treatType; - this.qualificationExpression = qualificationExpression; this.valuesFunction = null; this.valueCount = 0; this.attributeCount = 0; this.valueQuery = null; this.valuesClause = null; this.valuesAliases = null; - this.correlationParent = null; - this.correlationPath = null; + this.qualificationExpression = qualificationExpression; + this.aliasInfo = aliasInfo; + if (treatType != null) { + if (parent != null) { + List joinNodesForTreatConstraint = new ArrayList<>(parent.joinNodesForTreatConstraint.size() + 1); + joinNodesForTreatConstraint.addAll(parent.joinNodesForTreatConstraint); + joinNodesForTreatConstraint.add(this); + this.joinNodesForTreatConstraint = Collections.unmodifiableList(joinNodesForTreatConstraint); + } else { + this.joinNodesForTreatConstraint = Collections.singletonList(this); + } + } else { + if (parent != null) { + this.joinNodesForTreatConstraint = parent.joinNodesForTreatConstraint; + } else { + this.joinNodesForTreatConstraint = Collections.emptyList(); + } + } onUpdate(null); } - public JoinNode(JoinNode correlationParent, String correlationPath, String parentTreatType, JoinAliasInfo aliasInfo, Class propertyClass, String treatType, String qualificationExpression) { + private JoinNode(ManagedType nodeType, String valuesFunction, int valueCount, int attributeCount, Query valueQuery, String valuesClause, String valuesAliases, JoinAliasInfo aliasInfo) { this.parent = null; this.parentTreeNode = null; this.joinType = null; - this.correlationParent = correlationParent; - this.correlationPath = correlationPath; - this.parentTreatType = parentTreatType; + this.correlationParent = null; + this.correlationPath = null; + this.nodeType = nodeType; + this.treatType = null; + this.valuesFunction = valuesFunction; + this.valueCount = valueCount; + this.attributeCount = attributeCount; + this.valueQuery = valueQuery; + this.valuesClause = valuesClause; + this.valuesAliases = valuesAliases; + this.qualificationExpression = null; this.aliasInfo = aliasInfo; - this.propertyClass = propertyClass; - this.treatType = treatType; - this.valuesFunction = null; - this.valueCount = 0; - this.attributeCount = 0; - this.valueQuery = null; - this.valuesClause = null; - this.valuesAliases = null; - this.qualificationExpression = qualificationExpression; + this.joinNodesForTreatConstraint = Collections.emptyList(); onUpdate(null); } + + public static JoinNode createRootNode(EntityType nodeType, JoinAliasInfo aliasInfo) { + return new JoinNode(null, null, null, null, null, nodeType, null, null, aliasInfo); + } + + public static JoinNode createValuesRootNode(ManagedType nodeType, String valuesFunction, int valueCount, int attributeCount, Query valueQuery, String valuesClause, String valuesAliases, JoinAliasInfo aliasInfo) { + return new JoinNode(nodeType, valuesFunction, valueCount, attributeCount, valueQuery, valuesClause, valuesAliases, aliasInfo); + } + + public static JoinNode createCorrelationRootNode(JoinNode correlationParent, String correlationPath, ManagedType nodeType, EntityType treatType, JoinAliasInfo aliasInfo) { + return new JoinNode(null, null, null, correlationParent, correlationPath, nodeType, treatType, null, aliasInfo); + } + + public static JoinNode createEntityJoinNode(JoinNode parent, JoinType joinType, EntityType nodeType, JoinAliasInfo aliasInfo) { + return new JoinNode(parent, null, joinType, null, null, nodeType, null, null, aliasInfo); + } + + public static JoinNode createAssociationJoinNode(JoinNode parent, JoinTreeNode parentTreeNode, JoinType joinType, Type nodeType, EntityType treatType, String qualificationExpression, JoinAliasInfo aliasInfo) { + return new JoinNode(parent, parentTreeNode, joinType, null, null, nodeType, treatType, qualificationExpression, aliasInfo); + } + + public JoinNode cloneRootNode(JoinAliasInfo aliasInfo) { + // NOTE: no cloning of treatedJoinNodes and entityJoinNodes is intentional + JoinNode newNode; + if (valueQuery != null) { + newNode = createValuesRootNode((ManagedType) nodeType, valuesFunction, valueCount, attributeCount, valueQuery, valuesClause, valuesAliases, aliasInfo); + } else if (joinType == null) { + newNode = createRootNode((EntityType) nodeType, aliasInfo); + } else { + throw new UnsupportedOperationException("Cloning subqueries not yet implemented!"); + } + + return newNode; + } + + public JoinNode cloneJoinNode(JoinNode parent, JoinTreeNode parentTreeNode, JoinAliasInfo aliasInfo) { + // NOTE: no cloning of onPredicate, treatedJoinNodes and entityJoinNodes is intentional + JoinNode newNode; + if (parentTreeNode == null) { + newNode = createEntityJoinNode(parent, joinType, (EntityType) nodeType, aliasInfo); + } else { + newNode = createAssociationJoinNode(parent, parentTreeNode, joinType, nodeType, treatType, qualificationExpression, aliasInfo); + } + + newNode.fetch = fetch; + + return newNode; + } private void onUpdate(StateChange stateChange) { // Once mandatory, only a type change can cause a change of the cardinality mandatory @@ -275,6 +344,9 @@ public void accept(ExpressionModifierVisitor visitor for (JoinNode joinNode : entityJoinNodes) { joinNode.accept(visitor); } + for (JoinNode joinNode : treatedJoinNodes.values()) { + joinNode.accept(visitor); + } } public void accept(JoinNodeVisitor visitor) { @@ -287,6 +359,9 @@ public void accept(JoinNodeVisitor visitor) { for (JoinNode joinNode : entityJoinNodes) { joinNode.accept(visitor); } + for (JoinNode joinNode : treatedJoinNodes.values()) { + joinNode.accept(visitor); + } } public T accept(AbortableResultJoinNodeVisitor visitor) { @@ -312,10 +387,31 @@ public T accept(AbortableResultJoinNodeVisitor visitor) { return result; } } + for (JoinNode joinNode : treatedJoinNodes.values()) { + result = joinNode.accept(visitor); + + if (visitor.getStopValue().equals(result)) { + return result; + } + } return result; } + public JoinNode getTreatedJoinNode(EntityType type) { + String typeName = type.getJavaType().getName(); + JoinNode treatedNode = treatedJoinNodes.get(typeName); + if (treatedNode != null) { + return treatedNode; + } + + TreatedJoinAliasInfo treatedJoinAliasInfo = new TreatedJoinAliasInfo(this, type); + treatedNode = new JoinNode(treatedJoinAliasInfo); + treatedJoinAliasInfo.setJoinNode(treatedNode); + treatedJoinNodes.put(typeName, treatedNode); + return treatedNode; + } + public EnumSet getClauseDependencies() { return clauseDependencies; } @@ -332,10 +428,6 @@ public JoinAliasInfo getAliasInfo() { return aliasInfo; } - public void setAliasInfo(JoinAliasInfo aliasInfo) { - this.aliasInfo = aliasInfo; - } - public JoinType getJoinType() { return joinType; } @@ -357,6 +449,10 @@ public Map getNodes() { return nodes; } + public Map getTreatedJoinNodes() { + return treatedJoinNodes; + } + public JoinTreeNode getOrCreateTreeNode(String joinRelationName, Attribute attribute) { JoinTreeNode node = nodes.get(joinRelationName); @@ -376,18 +472,51 @@ public void addEntityJoin(JoinNode entityJoinNode) { entityJoinNodes.add(entityJoinNode); } - public String getParentTreatType() { - return parentTreatType; + public Type getNodeType() { + if (treatType != null) { + return treatType; + } + return nodeType; + } + + public EntityType getEntityType() { + if (treatType != null) { + return treatType; + } + if (nodeType instanceof EntityType) { + return (EntityType) nodeType; + } + + throw new IllegalArgumentException("Expected type of join node to be an entity but isn't: " + nodeType.getJavaType().getName()); } - public Class getPropertyClass() { - return propertyClass; + public ManagedType getManagedType() { + if (treatType != null) { + return treatType; + } + if (nodeType instanceof ManagedType) { + return (ManagedType) nodeType; + } + + throw new IllegalArgumentException("Expected type of join node to be a managed type but isn't: " + nodeType.getJavaType().getName()); } - public String getTreatType() { + public Type getBaseType() { + return nodeType; + } + + public EntityType getTreatType() { return treatType; } + public boolean isTreatJoinNode() { + return treatType != null && !(aliasInfo instanceof TreatedJoinAliasInfo); + } + + public boolean isTreatedJoinNode() { + return treatType != null && aliasInfo instanceof TreatedJoinAliasInfo; + } + public int getValueCount() { return valueCount; } @@ -445,6 +574,10 @@ public boolean hasCollections() { stack.addAll(node.getNodes().values()); } + for (JoinNode node : treatedJoinNodes.values()) { + stack.addAll(node.getNodes().values()); + } + while (!stack.isEmpty()) { JoinTreeNode treeNode = stack.remove(stack.size() - 1); @@ -471,6 +604,10 @@ Set getCollectionJoins() { stack.addAll(node.getNodes().values()); } + for (JoinNode node : treatedJoinNodes.values()) { + stack.addAll(node.getNodes().values()); + } + collectionJoins.addAll(entityJoinNodes); while (!stack.isEmpty()) { @@ -489,6 +626,10 @@ Set getCollectionJoins() { return collectionJoins; } + List getJoinNodesForTreatConstraint() { + return joinNodesForTreatConstraint; + } + public String getQualificationExpression() { return qualificationExpression; } @@ -534,7 +675,23 @@ public Expression createExpression(String field) { } } - public void appendAlias(StringBuilder sb, String property) { + public void appendDeReference(StringBuilder sb, String property) { + appendDeReference(sb, property, false); + } + + public void appendDeReference(StringBuilder sb, String property, boolean renderTreat) { + appendAlias(sb, renderTreat); + // If we have a valuesFunction, the property can only be "value" which is already handled in appendAlias + if (property != null && valuesFunction == null) { + sb.append('.').append(property); + } + } + + public void appendAlias(StringBuilder sb) { + appendAlias(sb, false); + } + + public void appendAlias(StringBuilder sb, boolean renderTreat) { if (valuesFunction != null) { // NOTE: property should always be null sb.append(valuesFunction).append('('); @@ -543,16 +700,33 @@ public void appendAlias(StringBuilder sb, String property) { sb.append(')'); } else { if (qualificationExpression != null) { + boolean hasTreat = renderTreat && treatType != null; + if (hasTreat) { + sb.append("TREAT("); + } + sb.append(qualificationExpression); sb.append('('); - sb.append(parent.getAlias()); + + if (renderTreat) { + parent.getAliasInfo().render(sb); + } else { + sb.append(parent.getAlias()); + } + sb.append(')'); - } else { - sb.append(aliasInfo.getAlias()); - } - if (property != null) { - sb.append('.').append(property); + if (hasTreat) { + sb.append(" AS "); + sb.append(treatType.getName()); + sb.append(')'); + } + } else { + if (renderTreat) { + aliasInfo.render(sb); + } else { + sb.append(aliasInfo.getAlias()); + } } } } @@ -566,7 +740,10 @@ public String getAlias() { @Override public Class getType() { - return propertyClass; + if (treatType != null) { + return treatType.getJavaType(); + } + return nodeType.getJavaType(); } public int getJoinDepth() { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JpaUtils.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JpaUtils.java index 634034fa62..5f33182511 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JpaUtils.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JpaUtils.java @@ -320,11 +320,7 @@ public static AttributeHolder getAttributeForJoining(EntityMetamodel metamodel, } } - return getAttributeForJoining(metamodel, baseNode.getPropertyClass(), baseNode.getTreatType(), expression, baseNodeAlias); - } - - public static AttributeHolder getAttributeForJoining(EntityMetamodel metamodel, Class type, String treatTypeName, Expression joinExpression, String baseNodeAlias) { - return getAttributeForJoining(metamodel, getManagedType(metamodel, type, treatTypeName).getJavaType(), joinExpression, baseNodeAlias); + return getAttributeForJoining(metamodel, baseNode.getType(), expression, baseNodeAlias); } public static AttributeHolder getAttributeForJoining(EntityMetamodel metamodel, Class type, Expression joinExpression, String baseNodeAlias) { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/PaginatedCriteriaBuilderImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/PaginatedCriteriaBuilderImpl.java index d7866f4b47..d8d059535e 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/PaginatedCriteriaBuilderImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/PaginatedCriteriaBuilderImpl.java @@ -468,10 +468,10 @@ protected String buildPageCountQueryString(boolean externalRepresentation) { private String buildPageCountQueryString(StringBuilder sbSelectFrom, boolean externalRepresentation) { JoinNode rootNode = joinManager.getRootNodeOrFail("Paginated criteria builders do not support multiple from clause elements!"); - Attribute idAttribute = JpaUtils.getIdAttribute(mainQuery.metamodel.entity(rootNode.getPropertyClass())); + Attribute idAttribute = JpaUtils.getIdAttribute(mainQuery.metamodel.entity(rootNode.getType())); String idName = idAttribute.getName(); StringBuilder idClause = new StringBuilder(100); - rootNode.appendAlias(idClause, idName); + rootNode.appendDeReference(idClause, idName); // Spaces are important to be able to reuse the string builder without copying String countString = jpaProvider.getCustomFunctionInvocation("COUNT_TUPLE", 1) + "'DISTINCT', " + idClause + ")"; sbSelectFrom.append("SELECT ").append(countString); @@ -526,9 +526,9 @@ private String appendSimplePageIdQueryString(StringBuilder sbSelectFrom) { queryGenerator.setAliasPrefix(PAGE_POSITION_ID_QUERY_ALIAS_PREFIX); JoinNode rootNode = joinManager.getRootNodeOrFail("Paginated criteria builders do not support multiple from clause elements!"); - String idName = JpaUtils.getIdAttribute(mainQuery.metamodel.entity(rootNode.getPropertyClass())).getName(); + String idName = JpaUtils.getIdAttribute(mainQuery.metamodel.entity(rootNode.getType())).getName(); StringBuilder idClause = new StringBuilder(PAGE_POSITION_ID_QUERY_ALIAS_PREFIX); - rootNode.appendAlias(idClause, idName); + rootNode.appendDeReference(idClause, idName); sbSelectFrom.append("SELECT ").append(idClause); // TODO: actually we should add the select clauses needed for order bys @@ -565,9 +565,9 @@ private String buildPageIdQueryString(boolean externalRepresentation) { private String buildPageIdQueryString(StringBuilder sbSelectFrom, boolean externalRepresentation) { JoinNode rootNode = joinManager.getRootNodeOrFail("Paginated criteria builders do not support multiple from clause elements!"); - String idName = JpaUtils.getIdAttribute(mainQuery.metamodel.entity(rootNode.getPropertyClass())).getName(); + String idName = JpaUtils.getIdAttribute(mainQuery.metamodel.entity(rootNode.getType())).getName(); StringBuilder idClause = new StringBuilder(100); - rootNode.appendAlias(idClause, idName); + rootNode.appendDeReference(idClause, idName); sbSelectFrom.append("SELECT ").append(idClause); @@ -622,7 +622,7 @@ protected String buildBaseQueryString(boolean externalRepresentation) { @Override protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean externalRepresentation) { JoinNode rootNode = joinManager.getRootNodeOrFail("Paginated criteria builders do not support multiple from clause elements!"); - String idName = JpaUtils.getIdAttribute(mainQuery.metamodel.entity(rootNode.getPropertyClass())).getName(); + String idName = JpaUtils.getIdAttribute(mainQuery.metamodel.entity(rootNode.getType())).getName(); selectManager.buildSelect(sbSelectFrom, false); @@ -635,7 +635,7 @@ protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean external List whereClauseConjuncts = new ArrayList<>(); joinManager.buildClause(sbSelectFrom, EnumSet.complementOf(EnumSet.of(ClauseType.SELECT, ClauseType.ORDER_BY)), null, false, externalRepresentation, whereClauseConjuncts, explicitVersionEntities, nodesToFetch); sbSelectFrom.append(" WHERE "); - rootNode.appendAlias(sbSelectFrom, idName); + rootNode.appendDeReference(sbSelectFrom, idName); sbSelectFrom.append(" IN :").append(ID_PARAM_NAME).append(""); for (String conjunct : whereClauseConjuncts) { 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 f480d08e01..919f2fead1 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 @@ -32,9 +32,20 @@ import com.blazebit.persistence.impl.expression.Subquery; import com.blazebit.persistence.impl.expression.SubqueryExpression; import com.blazebit.persistence.impl.expression.TreatExpression; +import com.blazebit.persistence.impl.predicate.BetweenPredicate; +import com.blazebit.persistence.impl.predicate.CompoundPredicate; import com.blazebit.persistence.impl.predicate.EqPredicate; +import com.blazebit.persistence.impl.predicate.ExistsPredicate; +import com.blazebit.persistence.impl.predicate.GePredicate; +import com.blazebit.persistence.impl.predicate.GtPredicate; 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.LePredicate; +import com.blazebit.persistence.impl.predicate.LikePredicate; +import com.blazebit.persistence.impl.predicate.LtPredicate; +import com.blazebit.persistence.impl.predicate.MemberOfPredicate; +import com.blazebit.persistence.impl.predicate.Predicate; import com.blazebit.persistence.impl.predicate.PredicateQuantifier; import com.blazebit.persistence.impl.util.TypeConverter; import com.blazebit.persistence.impl.util.TypeUtils; @@ -46,6 +57,7 @@ import javax.persistence.metamodel.ManagedType; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -62,6 +74,7 @@ public class ResolvingQueryGenerator extends SimpleQueryGenerator { private boolean resolveSelectAliases = true; private Set renderedJoinNodes; private ClauseType clauseType; + private Map treatedJoinNodesForConstraints; private final AliasManager aliasManager; private final ParameterManager parameterManager; private final EntityMetamodel metamodel; @@ -306,13 +319,15 @@ public void visit(PathExpression expression) { } } } - if (expression.getBaseNode() == null) { + JoinNode baseNode; + String field; + if ((baseNode = (JoinNode) expression.getBaseNode()) == null) { super.visit(expression); - } else if (expression.getField() == null) { + } else if ((field = expression.getField()) == null) { if (expression.isUsedInCollectionFunction() || renderAbsolutePath(expression)) { super.visit(expression); } else { - boolean valueFunction = needsValueFunction(expression) && jpaProvider.getCollectionValueFunction() != null; + boolean valueFunction = needsValueFunction(expression, baseNode, field) && jpaProvider.getCollectionValueFunction() != null; if (valueFunction) { sb.append(jpaProvider.getCollectionValueFunction()); @@ -323,49 +338,83 @@ public void visit(PathExpression expression) { sb.append(aliasPrefix); } - JoinNode baseNode = (JoinNode) expression.getBaseNode(); - baseNode.appendAlias(sb, null); + baseNode.appendAlias(sb); if (valueFunction) { sb.append(')'); } } } else { - // Thats e.g. TREAT(TREAT(alias).property) - if (expression.hasTreatedSubpath()) { - // Actually we know that the treated subpath must be the first part of the path - expression.getExpressions().get(0).accept(this); - sb.append(".").append(expression.getField()); - } else { - boolean valueFunction = needsValueFunction(expression) && jpaProvider.getCollectionValueFunction() != null; - JoinNode baseNode = (JoinNode) expression.getBaseNode(); + List treatedJoinNodes = baseNode.getJoinNodesForTreatConstraint(); + if (treatedJoinNodesForConstraints != null) { + for (JoinNode node : treatedJoinNodes) { + treatedJoinNodesForConstraints.put(node, Boolean.FALSE); + } + } - if (valueFunction) { - sb.append(jpaProvider.getCollectionValueFunction()); - sb.append('('); + boolean addTypeCaseWhen = !treatedJoinNodes.isEmpty() + && jpaProvider.needsTypeConstraintForColumnSharing() + && jpaProvider.isColumnShared(baseNode.getManagedType(), field); + if (addTypeCaseWhen) { + sb.append("CASE WHEN "); + boolean first = true; - if (aliasPrefix != null) { - sb.append(aliasPrefix); + for (int i = 0; i < treatedJoinNodes.size(); i++) { + JoinNode treatedJoinNode = treatedJoinNodes.get(i); + + // When the JPA provider supports rendering treat joins and we have a treat join node + // we skip the type constraint as that is already applied through the join + if (jpaProvider.supportsTreatJoin() && treatedJoinNode.isTreatJoinNode()) { + continue; } - baseNode.appendAlias(sb, null); - sb.append(')'); - sb.append(".").append(expression.getField()); - } else { - if (aliasPrefix != null) { - sb.append(aliasPrefix); + if (first) { + first = false; + } else { + sb.append(" AND "); } - baseNode.appendAlias(sb, expression.getField()); + sb.append("TYPE("); + sb.append(treatedJoinNode.getAlias()); + sb.append(") = "); + sb.append(treatedJoinNode.getTreatType().getName()); } + + sb.append(" THEN "); } + boolean valueFunction = needsValueFunction(expression, baseNode, field) && jpaProvider.getCollectionValueFunction() != null; + // NOTE: There is no need to check for whether the JPA provider support implicit downcasting here + // If it didn't, the query building would have already failed before. Here we just decide whether to render the treat or not + boolean renderTreat = jpaProvider.supportsRootTreat(); + + if (valueFunction) { + sb.append(jpaProvider.getCollectionValueFunction()); + sb.append('('); + + if (aliasPrefix != null) { + sb.append(aliasPrefix); + } + + baseNode.appendAlias(sb, renderTreat); + sb.append(')'); + sb.append(".").append(field); + } else { + if (aliasPrefix != null) { + sb.append(aliasPrefix); + } + + baseNode.appendDeReference(sb, field, renderTreat); + } + + if (addTypeCaseWhen) { + sb.append(" END"); + } } } - private boolean needsValueFunction(PathExpression expression) { - JoinNode baseNode = (JoinNode) expression.getBaseNode(); - return !expression.isCollectionKeyPath() && baseNode.getParentTreeNode() != null && baseNode.getParentTreeNode().isMap() && (expression.getField() == null || jpaProvider.supportsCollectionValueDereference()); + private boolean needsValueFunction(PathExpression expression, JoinNode baseNode, String field) { + return !expression.isCollectionKeyPath() && baseNode.getParentTreeNode() != null && baseNode.getParentTreeNode().isMap() && (field == null || jpaProvider.supportsCollectionValueDereference()); } private boolean renderAbsolutePath(PathExpression expression) { @@ -457,6 +506,7 @@ public void visit(InPredicate predicate) { Class associationType = getAssociationType(predicate.getLeft(), right); ParameterManager.ParameterValueTranformer tranformer = parameterTransformerFactory.getToEntityTranformer(associationType); param.setTranformer(tranformer); + renderEquality(predicate.getLeft(), right, predicate.isNegated(), PredicateQuantifier.ONE); } else if (right instanceof PathExpression) { renderEquality(predicate.getLeft(), right, predicate.isNegated(), PredicateQuantifier.ONE); } @@ -476,6 +526,9 @@ private Class getAssociationType(Expression expression1, Expression expressio @Override public void visit(final EqPredicate predicate) { renderEquality(predicate.getLeft(), predicate.getRight(), predicate.isNegated(), predicate.getQuantifier()); + if (predicate.isNegated()) { + flipTreatedJoinNodeConstraints(); + } } private void renderEquality(Expression left, Expression right, boolean negated, PredicateQuantifier quantifier) { @@ -552,9 +605,200 @@ public void visit(IsNullPredicate predicate) { predicate.getExpression().accept(this); if (predicate.isNegated()) { sb.append(" IS NOT NULL"); + flipTreatedJoinNodeConstraints(); } else { sb.append(" IS NULL"); } setParameterRenderingMode(oldParameterRenderingMode); } + + @Override + public void visit(IsEmptyPredicate predicate) { + super.visit(predicate); + if (predicate.isNegated()) { + flipTreatedJoinNodeConstraints(); + } + } + + @Override + public void visit(MemberOfPredicate predicate) { + super.visit(predicate); + if (predicate.isNegated()) { + flipTreatedJoinNodeConstraints(); + } + } + + @Override + public void visit(LikePredicate predicate) { + super.visit(predicate); + if (predicate.isNegated()) { + flipTreatedJoinNodeConstraints(); + } + } + + @Override + public void visit(BetweenPredicate predicate) { + super.visit(predicate); + if (predicate.isNegated()) { + flipTreatedJoinNodeConstraints(); + } + } + + @Override + public void visit(ExistsPredicate predicate) { + super.visit(predicate); + if (predicate.isNegated()) { + flipTreatedJoinNodeConstraints(); + } + } + + @Override + public void visit(GtPredicate predicate) { + super.visit(predicate); + if (predicate.isNegated()) { + flipTreatedJoinNodeConstraints(); + } + } + + @Override + public void visit(GePredicate predicate) { + super.visit(predicate); + if (predicate.isNegated()) { + flipTreatedJoinNodeConstraints(); + } + } + + @Override + public void visit(LtPredicate predicate) { + super.visit(predicate); + if (predicate.isNegated()) { + flipTreatedJoinNodeConstraints(); + } + } + + @Override + public void visit(LePredicate predicate) { + super.visit(predicate); + if (predicate.isNegated()) { + flipTreatedJoinNodeConstraints(); + } + } + + @Override + public void visit(CompoundPredicate predicate) { + BooleanLiteralRenderingContext oldConditionalContext = setBooleanLiteralRenderingContext(BooleanLiteralRenderingContext.PREDICATE); + ParameterRenderingMode oldParameterRenderingMode = setParameterRenderingMode(ParameterRenderingMode.PLACEHOLDER); + boolean parenthesisRequired = predicate.getChildren().size() > 1; + if (predicate.isNegated()) { + sb.append("NOT "); + if (parenthesisRequired) { + sb.append('('); + } + } + + if (predicate.getChildren().size() == 1) { + int startPosition = sb.length(); + Map oldTreatedJoinNodesForConstraints = treatedJoinNodesForConstraints; + treatedJoinNodesForConstraints = new LinkedHashMap<>(); + + predicate.getChildren().get(0).accept(this); + + insertTreatJoinConstraint(startPosition, sb.length()); + treatedJoinNodesForConstraints = oldTreatedJoinNodesForConstraints; + return; + } + final int startLen = sb.length(); + final String operator = " " + predicate.getOperator().toString() + " "; + final List children = predicate.getChildren(); + int size = children.size(); + Map oldTreatedJoinNodesForConstraints = treatedJoinNodesForConstraints; + treatedJoinNodesForConstraints = new LinkedHashMap<>(); + for (int i = 0; i < size; i++) { + int startPosition = sb.length(); + int endPosition; + + Predicate child = children.get(i); + if (child instanceof CompoundPredicate && ((CompoundPredicate) child).getOperator() != predicate.getOperator() && !child.isNegated()) { + sb.append("("); + int len = sb.length(); + child.accept(this); + // If the child was empty, we remove the opening parenthesis again + if (len == sb.length()) { + sb.deleteCharAt(len - 1); + endPosition = sb.length(); + } else { + sb.append(")"); + endPosition = sb.length(); + sb.append(operator); + } + } else { + child.accept(this); + endPosition = sb.length(); + sb.append(operator); + } + + insertTreatJoinConstraint(startPosition, endPosition); + treatedJoinNodesForConstraints.clear(); + } + + treatedJoinNodesForConstraints = oldTreatedJoinNodesForConstraints; + + // Delete the last operator only if the children actually generated something + if (startLen < sb.length()) { + sb.delete(sb.length() - operator.length(), sb.length()); + } + if (predicate.isNegated() && parenthesisRequired) { + sb.append(')'); + } + setBooleanLiteralRenderingContext(oldConditionalContext); + setParameterRenderingMode(oldParameterRenderingMode); + } + + private void flipTreatedJoinNodeConstraints() { + if (treatedJoinNodesForConstraints != null) { + for (Map.Entry entry : treatedJoinNodesForConstraints.entrySet()) { + if (entry.getValue() == Boolean.TRUE) { + entry.setValue(Boolean.FALSE); + } else { + entry.setValue(Boolean.TRUE); + } + } + } + } + + private boolean insertTreatJoinConstraint(int startPosition, int endPosition) { + if (!treatedJoinNodesForConstraints.isEmpty()) { + StringBuilder treatConditionBuilder = new StringBuilder(treatedJoinNodesForConstraints.size() * 40); + treatConditionBuilder.append('('); + for (Map.Entry entry : treatedJoinNodesForConstraints.entrySet()) { + JoinNode node = entry.getKey(); + // When the JPA provider supports rendering treat joins and we have a treat join node + // we skip the type constraint as that is already applied through the join + if (jpaProvider.supportsTreatJoin() && node.isTreatJoinNode()) { + continue; + } + + treatConditionBuilder.append("TYPE("); + treatConditionBuilder.append(node.getAlias()); + if (entry.getValue() == Boolean.TRUE) { + treatConditionBuilder.append(") <> "); + treatConditionBuilder.append(node.getTreatType().getName()); + treatConditionBuilder.append(" OR "); + } else { + treatConditionBuilder.append(") = "); + treatConditionBuilder.append(node.getTreatType().getName()); + treatConditionBuilder.append(" AND "); + } + } + + // Because we always have the open parenthesis as first char + if (treatConditionBuilder.length() > 1) { + sb.insert(endPosition, ')'); + sb.insert(startPosition, treatConditionBuilder); + return true; + } + } + + return false; + } } 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 929c5dd3fb..51ce469b4b 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 @@ -208,7 +208,7 @@ void buildGroupByClauses(final EntityMetamodel 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, null, rootNode.getPropertyClass()), false, false)); + resolveVisitor.visit(new PathExpression(path, new SimplePathReference(rootNode, null, rootNode.getType()), false, false)); queryGenerator.setClauseType(ClauseType.GROUP_BY); queryGenerator.setQueryBuffer(sb); @@ -268,7 +268,7 @@ void buildSelect(StringBuilder sb, boolean isInsertInto) { int size = selectInfos.size(); if (size == 0) { JoinNode rootNode = joinManager.getRootNodeOrFail("Empty select not allowed when having multiple roots!"); - rootNode.appendAlias(sb, null); + rootNode.appendAlias(sb); } else { // we must not replace select alias since we would loose the original expressions queryGenerator.setClauseType(ClauseType.SELECT); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/TreatedJoinAliasInfo.java b/core/impl/src/main/java/com/blazebit/persistence/impl/TreatedJoinAliasInfo.java new file mode 100644 index 0000000000..edf7e40f1f --- /dev/null +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/TreatedJoinAliasInfo.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014 - 2017 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 javax.persistence.metamodel.EntityType; + +/** + * This is the join alias info for "special" join nodes that aren't rendered as joins + * but only serve for providing a "treat-view" on an existing join node. + * + * @author Christian Beikov + * @since 1.2.0 + */ +public class TreatedJoinAliasInfo extends JoinAliasInfo { + + private final JoinNode treatedJoinNode; + private final EntityType treatType; + + public TreatedJoinAliasInfo(JoinNode treatedJoinNode, EntityType treatType) { + super( + treatedJoinNode.getAlias(), + "TREAT(" + treatedJoinNode.getAliasInfo().getAbsolutePath() + " AS " + treatType.getName() + ")", + treatedJoinNode.getAliasInfo().isImplicit(), + treatedJoinNode.getAliasInfo().isRootNode(), + treatedJoinNode.getAliasInfo().getAliasOwner() + ); + this.treatedJoinNode = treatedJoinNode; + this.treatType = treatType; + } + + public JoinNode getTreatedJoinNode() { + return treatedJoinNode; + } + + public EntityType getTreatType() { + return treatType; + } + + @Override + public String getAbsolutePath() { + return "TREAT(" + treatedJoinNode.getAliasInfo().getAbsolutePath() + " AS " + treatType.getName() + ")"; + } + + @Override + public void render(StringBuilder sb) { + sb.append("TREAT(").append(treatedJoinNode.getAliasInfo().getAlias()).append(" AS ").append(treatType.getName()).append(')'); + } +} diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/transform/SizeTransformationVisitor.java b/core/impl/src/main/java/com/blazebit/persistence/impl/transform/SizeTransformationVisitor.java index 4df93533c3..615c7c1b22 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/transform/SizeTransformationVisitor.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/transform/SizeTransformationVisitor.java @@ -47,6 +47,7 @@ import javax.persistence.metamodel.IdentifiableType; import javax.persistence.metamodel.ManagedType; import javax.persistence.metamodel.PluralAttribute; +import javax.persistence.metamodel.Type; import javax.persistence.metamodel.Type.PersistenceType; import java.util.*; @@ -160,16 +161,19 @@ private boolean requiresBlacklistedNode(PathExpression sizeArg) { private Expression getSizeExpression(ExpressionModifier parentModifier, PathExpression sizeArg) { JoinNode sizeArgJoin = (JoinNode) sizeArg.getBaseNode(); String property = sizeArg.getPathReference().getField(); - Class startClass = ((JoinNode) sizeArg.getBaseNode()).getPropertyClass(); + final Type nodeType = ((JoinNode) sizeArg.getBaseNode()).getBaseType(); + if (!(nodeType instanceof EntityType)) { + throw new IllegalArgumentException("Size on a collection owned by a non-entity type is not supported yet: " + sizeArg); + } + final EntityType startType = (EntityType) nodeType; AttributeHolder result = JpaUtils.getAttributeForJoining(metamodel, sizeArg); PluralAttribute targetAttribute = (PluralAttribute) result.getAttribute(); if (targetAttribute == null) { - throw new RuntimeException("Attribute [" + property + "] not found on class " + startClass.getName()); + throw new RuntimeException("Attribute [" + property + "] not found on class " + startType.getJavaType().getName()); } final PluralAttribute.CollectionType collectionType = targetAttribute.getCollectionType(); final boolean isElementCollection = targetAttribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION; - final EntityType startType = metamodel.entity(startClass); boolean subqueryRequired; if (isElementCollection) { @@ -185,14 +189,14 @@ private Expression getSizeExpression(ExpressionModifier parentModifier, PathExpr // build group by id clause List pathElementExpr = new ArrayList(); - String rootId = JpaUtils.getIdAttribute(metamodel.entity(startClass)).getName(); + String rootId = JpaUtils.getIdAttribute(startType).getName(); pathElementExpr.add(new PropertyExpression(sizeArgJoin.getAlias())); pathElementExpr.add(new PropertyExpression(rootId)); PathExpression groupByExpr = new PathExpression(pathElementExpr); String groupByExprString = groupByExpr.toString(); subqueryRequired = subqueryRequired || - !metamodel.entity(startClass).hasSingleIdAttribute() || + !startType.hasSingleIdAttribute() || joinManager.getRoots().size() > 1 || clause == ClauseType.JOIN || !isCountTransformationEnabled() || @@ -280,7 +284,8 @@ private Expression getSizeExpression(ExpressionModifier parentModifier, PathExpr AggregateExpression transformedExpr = transformedExpressionEntry.getTransformedExpression(); if (ExpressionUtils.isCustomFunctionInvocation(transformedExpr) && AbstractCountFunction.FUNCTION_NAME.equalsIgnoreCase(((StringLiteral) transformedExpr.getExpressions().get(0)).getValue())) { - if (!AbstractCountFunction.DISTINCT_QUALIFIER.equals(transformedExpr.getExpressions().get(1))) { + Expression possibleDistinct = transformedExpr.getExpressions().get(1); + if (!(possibleDistinct instanceof StringLiteral) || !AbstractCountFunction.DISTINCT_QUALIFIER.equals(((StringLiteral) possibleDistinct).getValue())) { transformedExpr.getExpressions().add(1, new StringLiteral(AbstractCountFunction.DISTINCT_QUALIFIER)); } } else { @@ -311,14 +316,18 @@ private AggregateExpression createCountFunction(boolean distinct, List startClass = sizeArgJoin.getPropertyClass(); + final Type nodeType = sizeArgJoin.getNodeType(); + if (!(nodeType instanceof EntityType)) { + throw new IllegalArgumentException("Size on a collection owned by a non-entity type is not supported yet: " + sizeArg); + } + final EntityType startType = (EntityType) nodeType; Subquery countSubquery = (Subquery) subqueryInitFactory.createSubqueryInitiator(null, new SubqueryBuilderListenerImpl(), false) .from(sizeArg.clone(true).toString()) .select("COUNT(*)"); List pathElementExpr = new ArrayList(); - String rootId = JpaUtils.getIdAttribute(metamodel.entity(startClass)).getName(); + String rootId = JpaUtils.getIdAttribute(startType).getName(); pathElementExpr.add(new PropertyExpression(sizeArgJoin.getAlias())); pathElementExpr.add(new PropertyExpression(rootId)); PathExpression groupByExpr = new PathExpression(pathElementExpr); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/transform/SizeTransformerGroup.java b/core/impl/src/main/java/com/blazebit/persistence/impl/transform/SizeTransformerGroup.java index c0057e9a64..628f65c3c3 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/transform/SizeTransformerGroup.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/transform/SizeTransformerGroup.java @@ -84,7 +84,7 @@ public void afterGlobalTransformation() { PathReference generatedJoin = requiredJoinExpression.getPathReference(); ((JoinNode) generatedJoin.getBaseNode()).getClauseDependencies().addAll(lateJoinEntry.getClauseDependencies()); for (int i = 1; i < lateJoinEntry.getPathsToJoin().size(); i++) { - lateJoinEntry.getPathsToJoin().get(i).setPathReference(new SimplePathReference(generatedJoin.getBaseNode(), generatedJoin.getField(), null, generatedJoin.getType())); + lateJoinEntry.getPathsToJoin().get(i).setPathReference(new SimplePathReference(generatedJoin.getBaseNode(), generatedJoin.getField(), generatedJoin.getType())); } } } diff --git a/core/parser/src/main/java/com/blazebit/persistence/impl/EntityMetamodel.java b/core/parser/src/main/java/com/blazebit/persistence/impl/EntityMetamodel.java index eade99a8f3..783337cfe3 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/EntityMetamodel.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/EntityMetamodel.java @@ -19,6 +19,7 @@ import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.ManagedType; import javax.persistence.metamodel.Metamodel; +import javax.persistence.metamodel.Type; /** * This is a wrapper around the JPA {@link javax.persistence.metamodel.Metamodel} that allows additionally efficient access by other attributes than a Class. @@ -38,4 +39,5 @@ public interface EntityMetamodel extends Metamodel { public ManagedType getManagedType(Class cls); + public Type type(Class cls); } 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 f8e1b27fe8..f0ea7ab502 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 @@ -152,10 +152,10 @@ protected String escapeCharacter(char character) { public void visit(final CompoundPredicate predicate) { BooleanLiteralRenderingContext oldConditionalContext = setBooleanLiteralRenderingContext(BooleanLiteralRenderingContext.PREDICATE); ParameterRenderingMode oldParameterRenderingMode = setParameterRenderingMode(ParameterRenderingMode.PLACEHOLDER); - boolean paranthesisRequired = predicate.getChildren().size() > 1; + boolean parenthesisRequired = predicate.getChildren().size() > 1; if (predicate.isNegated()) { sb.append("NOT "); - if (paranthesisRequired) { + if (parenthesisRequired) { sb.append('('); } } @@ -165,7 +165,7 @@ public void visit(final CompoundPredicate predicate) { return; } final int startLen = sb.length(); - final String and = " " + predicate.getOperator().toString() + " "; + final String operator = " " + predicate.getOperator().toString() + " "; List children = predicate.getChildren(); int size = children.size(); for (int i = 0; i < size; i++) { @@ -174,26 +174,24 @@ public void visit(final CompoundPredicate predicate) { sb.append("("); int len = sb.length(); child.accept(this); + // If the child was empty, we remove the opening parenthesis again if (len == sb.length()) { sb.deleteCharAt(len - 1); } else { sb.append(")"); - sb.append(and); + sb.append(operator); } - } else { - int len = sb.length(); child.accept(this); - if (len < sb.length()) { - sb.append(and); - } + sb.append(operator); } } + // Delete the last operator only if the children actually generated something if (startLen < sb.length()) { - sb.delete(sb.length() - and.length(), sb.length()); + sb.delete(sb.length() - operator.length(), sb.length()); } - if (predicate.isNegated() && paranthesisRequired) { + if (predicate.isNegated() && parenthesisRequired) { sb.append(')'); } setBooleanLiteralRenderingContext(oldConditionalContext); 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 040bfb218a..e460effdd5 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 @@ -90,7 +90,12 @@ private Expression createExpression(RuleInvoker ruleInvoker, String expression, CommonTokenStream tokens = new CommonTokenStream(l); JPQLSelectExpressionParser p = new JPQLSelectExpressionParser(tokens, allowCaseWhen, allowQuantifiedPredicates, allowTreatJoinExtension); configureParser(p); - ParserRuleContext ctx = ruleInvoker.invokeRule(p); + ParserRuleContext ctx; + try { + ctx = ruleInvoker.invokeRule(p); + } catch (SyntaxErrorException ex) { + throw new SyntaxErrorException("Could not parse expression '" + expression + "', " + ex.getMessage(), ex); + } if (LOG.isLoggable(Level.FINEST)) { LOG.finest(ctx.toStringTree()); 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 649d027112..cb6d2ecbe9 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 @@ -132,14 +132,6 @@ 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. 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 7ad9df5846..228831166e 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 @@ -30,8 +30,6 @@ public interface PathReference { public String getField(); - public String getTreatTypeName(); - /** * Returns the type of the path reference. * 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 f3f26ca04e..44e96a8866 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 @@ -21,13 +21,11 @@ public class SimplePathReference implements PathReference { private final BaseNode baseNode; private final String field; - private final String typeName; private final Class type; - public SimplePathReference(BaseNode baseNode, String field, String typeName, Class type) { + public SimplePathReference(BaseNode baseNode, String field, Class type) { this.baseNode = baseNode; this.field = field; - this.typeName = typeName; this.type = type; } @@ -41,11 +39,6 @@ public String getField() { return field; } - @Override - public String getTreatTypeName() { - return typeName; - } - @Override public Class getType() { return type; @@ -57,7 +50,6 @@ public int hashCode() { 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; } @@ -87,13 +79,6 @@ public boolean equals(Object obj) { } 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/SyntaxErrorException.java b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/SyntaxErrorException.java index 5f73e06471..e31d2bc47f 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/impl/expression/SyntaxErrorException.java +++ b/core/parser/src/main/java/com/blazebit/persistence/impl/expression/SyntaxErrorException.java @@ -36,4 +36,8 @@ public SyntaxErrorException(Throwable t) { public SyntaxErrorException(String msg) { super(msg); } + + public SyntaxErrorException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionJoinTestHibernate.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionJoinTestHibernate.java index 2078f4c833..29e1647f42 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionJoinTestHibernate.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionJoinTestHibernate.java @@ -48,9 +48,9 @@ public void testOneToManyMappedBy() { assertEquals("SELECT indexedNodesMappedBy_0_1, " + joinAliasValue("keyedNodesMappedBy_default_1") + " FROM Root2 r" + " LEFT JOIN r.indexedNodesMappedBy indexedNodesMappedBy_0_1" - + onClause(" (INDEX(indexedNodesMappedBy_0_1) = 0) ") + + onClause("INDEX(indexedNodesMappedBy_0_1) = 0") + " LEFT JOIN r.keyedNodesMappedBy keyedNodesMappedBy_default_1" - + onClause(" (KEY(keyedNodesMappedBy_default_1) = 'default')"), criteria.getQueryString()); + + onClause("KEY(keyedNodesMappedBy_default_1) = 'default'"), criteria.getQueryString()); criteria.getResultList(); } diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/DeletePolymorphicTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/DeletePolymorphicTest.java index b20f988ab4..9c4bcea4b7 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/DeletePolymorphicTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/DeletePolymorphicTest.java @@ -19,7 +19,6 @@ import com.blazebit.persistence.CriteriaBuilder; import com.blazebit.persistence.DeleteCriteriaBuilder; import com.blazebit.persistence.ReturningResult; -import com.blazebit.persistence.UpdateCriteriaBuilder; import com.blazebit.persistence.testsuite.base.category.NoDatanucleus; import com.blazebit.persistence.testsuite.base.category.NoEclipselink; import com.blazebit.persistence.testsuite.base.category.NoFirebird; @@ -38,9 +37,9 @@ import com.blazebit.persistence.testsuite.entity.PolymorphicSub1; import com.blazebit.persistence.testsuite.entity.PolymorphicSub2; import com.blazebit.persistence.testsuite.entity.StringIdCTE; -import com.blazebit.persistence.testsuite.entity.TablePerClassBase; -import com.blazebit.persistence.testsuite.entity.TablePerClassSub1; -import com.blazebit.persistence.testsuite.entity.TablePerClassSub2; +import com.blazebit.persistence.testsuite.entity.TPCBase; +import com.blazebit.persistence.testsuite.entity.TPCSub2; +import com.blazebit.persistence.testsuite.entity.TPCSub1; import com.blazebit.persistence.testsuite.tx.TxVoidWork; import org.junit.Before; import org.junit.Ignore; @@ -63,8 +62,8 @@ @Ignore("Has to be implemented as part of #345") public class DeletePolymorphicTest extends AbstractCoreTest { - TablePerClassSub1 tpc1; - TablePerClassSub2 tpc2; + TPCSub1 tpc1; + TPCSub2 tpc2; PolymorphicSub1 joined1; PolymorphicSub2 joined2; PolymorphicPropertySub1 st1; @@ -76,9 +75,9 @@ protected Class[] getEntityClasses() { IdHolderCTE.class, StringIdCTE.class, IntIdEntity.class, - TablePerClassBase.class, - TablePerClassSub1.class, - TablePerClassSub2.class, + TPCBase.class, + TPCSub1.class, + TPCSub2.class, PolymorphicBase.class, PolymorphicSub1.class, PolymorphicSub2.class, @@ -95,8 +94,8 @@ public void setUp() { @Override public void work(EntityManager em) { // Table per class data - tpc1 = new TablePerClassSub1(1L, "TPC1"); - tpc2 = new TablePerClassSub2(2L, "TPC2"); + tpc1 = new TPCSub1(1L, "TPC1"); + tpc2 = new TPCSub2(2L, "TPC2"); em.persist(tpc1); em.persist(tpc2); @@ -129,7 +128,7 @@ public void testTablePerClass() { transactional(new TxVoidWork() { @Override public void work(EntityManager em) { - final DeleteCriteriaBuilder cb = cbf.delete(em, TablePerClassBase.class, "t"); + final DeleteCriteriaBuilder cb = cbf.delete(em, TPCBase.class, "t"); cb.where("base").isNotNull(); String expected = "DELETE FROM TablePerClassBase t WHERE t.base IS NOT NULL"; @@ -137,7 +136,7 @@ public void work(EntityManager em) { int updateCount = cb.executeUpdate(); assertEquals(2, updateCount); - assertTrue(cbf.create(em, TablePerClassBase.class).getResultList().isEmpty()); + assertTrue(cbf.create(em, TPCBase.class).getResultList().isEmpty()); } }); } @@ -186,9 +185,9 @@ public void testTablePerClassWithCte() { transactional(new TxVoidWork() { @Override public void work(EntityManager em) { - final DeleteCriteriaBuilder cb = cbf.delete(em, TablePerClassBase.class, "t"); + final DeleteCriteriaBuilder cb = cbf.delete(em, TPCBase.class, "t"); cb.with(IdHolderCTE.class) - .from(TablePerClassBase.class, "t") + .from(TPCBase.class, "t") .bind("id").select("t.id") .end(); cb.where("id").in() @@ -206,7 +205,7 @@ public void work(EntityManager em) { int updateCount = cb.executeUpdate(); assertEquals(2, updateCount); - assertTrue(cbf.create(em, TablePerClassBase.class).getResultList().isEmpty()); + assertTrue(cbf.create(em, TPCBase.class).getResultList().isEmpty()); } }); } @@ -284,7 +283,7 @@ public void testTablePerClassReturning() { transactional(new TxVoidWork() { @Override public void work(EntityManager em) { - final DeleteCriteriaBuilder cb = cbf.delete(em, TablePerClassBase.class, "t"); + final DeleteCriteriaBuilder cb = cbf.delete(em, TPCBase.class, "t"); cb.where("base").isNotNull(); String expected = "DELETE FROM TablePerClassBase t WHERE t.base IS NOT NULL"; @@ -294,7 +293,7 @@ public void work(EntityManager em) { assertEquals(2, returningResult.getUpdateCount()); assertTrue(returningResult.getResultList().contains("TPC1")); assertTrue(returningResult.getResultList().contains("TPC2")); - assertTrue(cbf.create(em, TablePerClassBase.class).getResultList().isEmpty()); + assertTrue(cbf.create(em, TPCBase.class).getResultList().isEmpty()); } }); } @@ -352,7 +351,7 @@ public void testTablePerClassReturningInCTE() { public void work(EntityManager em) { final CriteriaBuilder cb = cbf.create(em, String.class) .withReturning(StringIdCTE.class) - .delete(TablePerClassBase.class, "t") + .delete(TPCBase.class, "t") .where("base").isNotNull() .returning("id", "base") .end() @@ -370,7 +369,7 @@ public void work(EntityManager em) { assertEquals(2, result.size()); assertTrue(result.contains("TPC1")); assertTrue(result.contains("TPC2")); - assertTrue(cbf.create(em, TablePerClassBase.class).getResultList().isEmpty()); + assertTrue(cbf.create(em, TPCBase.class).getResultList().isEmpty()); } }); } @@ -452,12 +451,12 @@ public void testTablePerClassReturningInCTESelectOld() { public void work(EntityManager em) { final CriteriaBuilder cb = cbf.create(em, String.class) .withReturning(IdHolderCTE.class) - .delete(TablePerClassBase.class, "t") + .delete(TPCBase.class, "t") .where("base").isNotNull() .returning("id", "id") .end() .from(IdHolderCTE.class, "cte") - .fromOld(TablePerClassBase.class, "t") + .fromOld(TPCBase.class, "t") .where("t.id").eqExpression("cte.id") .select("t.base"); @@ -472,7 +471,7 @@ public void work(EntityManager em) { assertEquals(2, result.size()); assertTrue(result.contains("TPC1")); assertTrue(result.contains("TPC2")); - assertTrue(cbf.create(em, TablePerClassBase.class).getResultList().isEmpty()); + assertTrue(cbf.create(em, TPCBase.class).getResultList().isEmpty()); } }); } @@ -555,11 +554,11 @@ public void testTablePerClassReturningInCTESelectNew() { public void work(EntityManager em) { final CriteriaBuilder cb = cbf.create(em, Tuple.class) .withReturning(IdHolderCTE.class) - .delete(TablePerClassBase.class, "t") + .delete(TPCBase.class, "t") .where("base").isNotNull() .returning("id", "id") .end() - .fromNew(TablePerClassBase.class, "t") + .fromNew(TPCBase.class, "t") .rightJoinOn(IdHolderCTE.class, "cte") .on("t.id").eqExpression("cte.id") .end() @@ -580,7 +579,7 @@ public void work(EntityManager em) { assertNull(result.get(0).get(1)); assertEquals(tpc2.getId(), result.get(1).get(0)); assertNull(result.get(1).get(1)); - assertTrue(cbf.create(em, TablePerClassBase.class).getResultList().isEmpty()); + assertTrue(cbf.create(em, TPCBase.class).getResultList().isEmpty()); } }); } diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/OrderByTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/OrderByTest.java index c13edcdcc8..522c398d5c 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/OrderByTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/OrderByTest.java @@ -16,9 +16,11 @@ package com.blazebit.persistence.testsuite; +import static com.googlecode.catchexception.CatchException.verifyException; import static org.junit.Assert.assertEquals; import com.blazebit.persistence.testsuite.base.category.NoDatanucleus; +import com.googlecode.catchexception.CatchException; import org.junit.Assert; import org.junit.Test; @@ -124,11 +126,7 @@ public void testOrderByFunctionCompatibleMode(){ config.setProperty(ConfigurationProperties.COMPATIBLE_MODE, "true"); cbf = config.createCriteriaBuilderFactory(em.getEntityManagerFactory()); CriteriaBuilder criteria = cbf.create(em, Document.class, "d"); - try { - criteria.orderByAsc("SIZE(d.partners)"); - Assert.fail("Expected SyntaxErrorException"); - } catch (SyntaxErrorException ex) { - } + verifyException(criteria, SyntaxErrorException.class).orderByAsc("SIZE(d.partners)"); } @Test diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/SelectPolymorphicTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/SelectPolymorphicTest.java index 2e1172c053..ab1d0f22ca 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/SelectPolymorphicTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/SelectPolymorphicTest.java @@ -22,12 +22,11 @@ import com.blazebit.persistence.testsuite.base.category.NoMySQL; import com.blazebit.persistence.testsuite.base.category.NoOpenJPA; import com.blazebit.persistence.testsuite.entity.IdHolderCTE; -import com.blazebit.persistence.testsuite.entity.TablePerClassBase; -import com.blazebit.persistence.testsuite.entity.TablePerClassSub1; -import com.blazebit.persistence.testsuite.entity.TablePerClassSub2; +import com.blazebit.persistence.testsuite.entity.TPCBase; +import com.blazebit.persistence.testsuite.entity.TPCSub1; +import com.blazebit.persistence.testsuite.entity.TPCSub2; import com.blazebit.persistence.testsuite.tx.TxVoidWork; import org.junit.Assert; -import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -46,9 +45,9 @@ public class SelectPolymorphicTest extends AbstractCoreTest { protected Class[] getEntityClasses() { return new Class[] { IdHolderCTE.class, - TablePerClassBase.class, - TablePerClassSub1.class, - TablePerClassSub2.class + TPCBase.class, + TPCSub1.class, + TPCSub2.class }; } @@ -58,8 +57,8 @@ public void setUpOnce() { transactional(new TxVoidWork() { @Override public void work(EntityManager em) { - TablePerClassSub1 entity1 = new TablePerClassSub1(1L, "test1"); - TablePerClassSub2 entity2 = new TablePerClassSub2(2L, "test2"); + TPCSub1 entity1 = new TPCSub1(1L, "test1"); + TPCSub2 entity2 = new TPCSub2(2L, "test2"); em.persist(entity1); em.persist(entity2); } @@ -70,9 +69,9 @@ public void work(EntityManager em) { @Test @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class, NoMySQL.class }) public void testSelectTablePerClassWithCte() throws Exception { - CriteriaBuilder cb = cbf.create(em, TablePerClassBase.class, "t") + CriteriaBuilder cb = cbf.create(em, TPCBase.class, "t") .with(IdHolderCTE.class) - .from(TablePerClassBase.class, "t") + .from(TPCBase.class, "t") .bind("id").select("t.id") .end() .where("id").in() @@ -81,15 +80,15 @@ public void testSelectTablePerClassWithCte() throws Exception { .end(); String expected = "WITH IdHolderCTE(id) AS(\n" + - "SELECT t.id FROM TablePerClassBase t\n" + + "SELECT t.id FROM TPCBase t\n" + ")\n" + - "SELECT t FROM TablePerClassBase t WHERE t.id IN (" + + "SELECT t FROM TPCBase t WHERE t.id IN (" + "SELECT cte.id FROM IdHolderCTE cte" + ")"; assertEquals(expected, cb.getQueryString()); - List result = cb.getResultList(); + List result = cb.getResultList(); Assert.assertEquals(2, result.size()); } diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/TablePerClassTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/TablePerClassTest.java index a06265e1db..372fa04d7e 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/TablePerClassTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/TablePerClassTest.java @@ -17,12 +17,11 @@ package com.blazebit.persistence.testsuite; import com.blazebit.persistence.CriteriaBuilder; -import com.blazebit.persistence.testsuite.entity.TablePerClassBase; -import com.blazebit.persistence.testsuite.entity.TablePerClassSub1; -import com.blazebit.persistence.testsuite.entity.TablePerClassSub2; +import com.blazebit.persistence.testsuite.entity.TPCBase; +import com.blazebit.persistence.testsuite.entity.TPCSub1; +import com.blazebit.persistence.testsuite.entity.TPCSub2; import com.blazebit.persistence.testsuite.tx.TxVoidWork; import org.junit.Assert; -import org.junit.Before; import org.junit.Test; import javax.persistence.EntityManager; @@ -39,9 +38,9 @@ public class TablePerClassTest extends AbstractCoreTest { @Override protected Class[] getEntityClasses() { return new Class[] { - TablePerClassBase.class, - TablePerClassSub1.class, - TablePerClassSub2.class + TPCBase.class, + TPCSub1.class, + TPCSub2.class }; } @@ -51,8 +50,8 @@ public void setUpOnce() { transactional(new TxVoidWork() { @Override public void work(EntityManager em) { - TablePerClassSub1 entity1 = new TablePerClassSub1(1L, "test1"); - TablePerClassSub2 entity2 = new TablePerClassSub2(2L, "test2"); + TPCSub1 entity1 = new TPCSub1(1L, "test1"); + TPCSub2 entity2 = new TPCSub2(2L, "test2"); em.persist(entity1); em.persist(entity2); } @@ -61,9 +60,9 @@ public void work(EntityManager em) { @Test public void buildingEntityMetamodelForTablePerClassEntitiesWorks() throws Exception { - CriteriaBuilder cb = cbf.create(em, TablePerClassBase.class); + CriteriaBuilder cb = cbf.create(em, TPCBase.class); - List result = cb.getResultList(); + List result = cb.getResultList(); Assert.assertEquals(2, result.size()); } 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 index 8da70994a7..bd4caf593d 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/TreatTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/TreatTest.java @@ -17,13 +17,16 @@ package com.blazebit.persistence.testsuite; import com.blazebit.persistence.CriteriaBuilder; +import com.blazebit.persistence.impl.expression.SyntaxErrorException; import com.blazebit.persistence.testsuite.base.category.NoDatanucleus; import com.blazebit.persistence.testsuite.base.category.NoDatanucleus4; import com.blazebit.persistence.testsuite.base.category.NoEclipselink; import com.blazebit.persistence.testsuite.entity.*; +import com.googlecode.catchexception.CatchException; import org.junit.Test; import org.junit.experimental.categories.Category; +import static com.googlecode.catchexception.CatchException.verifyException; import static org.junit.Assert.assertEquals; /** @@ -44,7 +47,21 @@ protected Class[] getEntityClasses() { } @Test - // NOTE: Apparently a bug in datanucleus? TODO: report the error + public void treatAsExpressionRootNotAllowed() { + CriteriaBuilder criteria = cbf.create(em, Integer.class); + criteria.from(PolymorphicBase.class, "p"); + verifyException(criteria, SyntaxErrorException.class).select("TREAT(p AS PolymorphicSub1)"); + } + + @Test + public void treatOfTreatAsExpressionRootNotAllowed() { + CriteriaBuilder criteria = cbf.create(em, Integer.class); + criteria.from(PolymorphicBase.class, "p"); + verifyException(criteria, SyntaxErrorException.class).select("TREAT(TREAT(p AS PolymorphicSub1).parent1 AS PolymorphicSub1)"); + } + + @Test + // NOTE: Datanucleus does not support root treats properly with joined inheritance. Maybe a bug? TODO: report the error @Category({ NoDatanucleus.class }) public void implicitJoinTreatedRoot() { CriteriaBuilder criteria = cbf.create(em, Integer.class); @@ -55,6 +72,8 @@ public void implicitJoinTreatedRoot() { } @Test + // NOTE: Datanucleus does not support root treats properly with joined inheritance. Maybe a bug? TODO: report the error + @Category({ NoDatanucleus.class }) public void implicitJoinTreatedRelation() { CriteriaBuilder criteria = cbf.create(em, Integer.class); criteria.from(PolymorphicBase.class, "p"); @@ -65,7 +84,21 @@ public void implicitJoinTreatedRelation() { @Test // NOTE: Datanucleus4 reports: We do not currently support JOIN to TREAT - @Category({ NoDatanucleus4.class }) + // NOTE: Datanucleus does not support root treats properly with joined inheritance. Maybe a bug? TODO: report the error + // NOTE: Eclipselink does not support root treat joins i.e. "JOIN TREAT(..).relation" + @Category({ NoDatanucleus.class , NoDatanucleus4.class, NoEclipselink.class }) + public void implicitJoinTreatedRootTreatJoin() { + CriteriaBuilder criteria = cbf.create(em, Integer.class); + criteria.from(PolymorphicBase.class, "p"); + criteria.select("TREAT(TREAT(p AS PolymorphicSub1).parent1 AS PolymorphicSub1).sub1Value"); + assertEquals("SELECT " + treatRoot("parent1_1", PolymorphicSub1.class, "sub1Value") + " FROM PolymorphicBase p LEFT JOIN " + treatRootJoin("p", PolymorphicSub1.class, "parent1") + " parent1_1", criteria.getQueryString()); + criteria.getResultList(); + } + + @Test + // NOTE: Datanucleus4 reports: We do not currently support JOIN to TREAT + // NOTE: Datanucleus does not support root treats properly with joined inheritance. Maybe a bug? TODO: report the error + @Category({ NoDatanucleus.class, NoDatanucleus4.class }) public void joinTreatedRelation() { CriteriaBuilder criteria = cbf.create(em, Integer.class); criteria.from(PolymorphicBase.class, "p"); @@ -76,9 +109,8 @@ public void joinTreatedRelation() { } @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? - // NOTE: Apparently a bug in datanucleus? TODO: report the error - // Eclipselink does not support dereferencing of TREAT join path elements + // NOTE: With datanucleus this only fails with INNER JOIN but works with left join. Maybe a bug? TODO: report the error + // Eclipselink and Datanucleus do not support root treat joins @Category({ NoDatanucleus.class, NoEclipselink.class}) public void joinTreatedRoot() { CriteriaBuilder criteria = cbf.create(em, Integer.class); @@ -90,8 +122,7 @@ public void joinTreatedRoot() { } @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? - // NOTE: Apparently a bug in datanucleus? TODO: report the error + // NOTE: With datanucleus this only fails with INNER JOIN but works with left join. Maybe a bug? TODO: report the error // Eclipselink does not support dereferencing of TREAT join path elements @Category({ NoDatanucleus.class, NoEclipselink.class }) public void joinTreatedRootEmbeddable() { @@ -104,8 +135,7 @@ public void joinTreatedRootEmbeddable() { } @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? - // NOTE: Apparently a bug in datanucleus? TODO: report the error + // NOTE: With datanucleus this only fails with INNER JOIN but works with left join. Maybe a bug? TODO: report the error // Eclipselink does not support dereferencing of TREAT join path elements @Category({ NoDatanucleus.class, NoEclipselink.class }) public void selectTreatedRootEmbeddable() { @@ -117,8 +147,7 @@ public void selectTreatedRootEmbeddable() { } @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? - // NOTE: Apparently a bug in datanucleus? TODO: report the error + // NOTE: With datanucleus this only fails with INNER JOIN but works with left join. Maybe a bug? TODO: report the error @Category({ NoDatanucleus.class }) public void treatJoinTreatedRootRelation() { CriteriaBuilder criteria = cbf.create(em, Integer.class); diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/UpdatePolymorphicTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/UpdatePolymorphicTest.java index ca8ad4b6fb..1ae7f51503 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/UpdatePolymorphicTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/UpdatePolymorphicTest.java @@ -37,9 +37,9 @@ import com.blazebit.persistence.testsuite.entity.PolymorphicSub1; import com.blazebit.persistence.testsuite.entity.PolymorphicSub2; import com.blazebit.persistence.testsuite.entity.StringIdCTE; -import com.blazebit.persistence.testsuite.entity.TablePerClassBase; -import com.blazebit.persistence.testsuite.entity.TablePerClassSub1; -import com.blazebit.persistence.testsuite.entity.TablePerClassSub2; +import com.blazebit.persistence.testsuite.entity.TPCBase; +import com.blazebit.persistence.testsuite.entity.TPCSub1; +import com.blazebit.persistence.testsuite.entity.TPCSub2; import com.blazebit.persistence.testsuite.tx.TxVoidWork; import org.junit.Before; import org.junit.Ignore; @@ -67,9 +67,9 @@ protected Class[] getEntityClasses() { IdHolderCTE.class, StringIdCTE.class, IntIdEntity.class, - TablePerClassBase.class, - TablePerClassSub1.class, - TablePerClassSub2.class, + TPCBase.class, + TPCSub1.class, + TPCSub2.class, PolymorphicBase.class, PolymorphicSub1.class, PolymorphicSub2.class, @@ -86,8 +86,8 @@ public void setUp() { @Override public void work(EntityManager em) { // Table per class data - TablePerClassSub1 tpc1 = new TablePerClassSub1(1L, "TPC1"); - TablePerClassSub2 tpc2 = new TablePerClassSub2(2L, "TPC2"); + TPCSub1 tpc1 = new TPCSub1(1L, "TPC1"); + TPCSub2 tpc2 = new TPCSub2(2L, "TPC2"); em.persist(tpc1); em.persist(tpc2); @@ -120,7 +120,7 @@ public void testTablePerClass() { transactional(new TxVoidWork() { @Override public void work(EntityManager em) { - final UpdateCriteriaBuilder cb = cbf.update(em, TablePerClassBase.class, "t"); + final UpdateCriteriaBuilder cb = cbf.update(em, TPCBase.class, "t"); cb.setExpression("base", "CONCAT(base, ' - 1')"); String expected = "UPDATE TablePerClassBase t SET t.base = CONCAT(base,' - 1')"; @@ -174,9 +174,9 @@ public void testTablePerClassWithCte() { transactional(new TxVoidWork() { @Override public void work(EntityManager em) { - final UpdateCriteriaBuilder cb = cbf.update(em, TablePerClassBase.class, "t"); + final UpdateCriteriaBuilder cb = cbf.update(em, TPCBase.class, "t"); cb.with(IdHolderCTE.class) - .from(TablePerClassBase.class, "t") + .from(TPCBase.class, "t") .bind("id").select("t.id") .end(); cb.where("id").in() @@ -272,7 +272,7 @@ public void testTablePerClassReturning() { transactional(new TxVoidWork() { @Override public void work(EntityManager em) { - final UpdateCriteriaBuilder cb = cbf.update(em, TablePerClassBase.class, "t"); + final UpdateCriteriaBuilder cb = cbf.update(em, TPCBase.class, "t"); cb.setExpression("base", "CONCAT(base, ' - 1')"); String expected = "UPDATE TablePerClassBase t SET t.base = CONCAT(base,' - 1')"; @@ -337,7 +337,7 @@ public void testTablePerClassReturningInCTE() { public void work(EntityManager em) { final CriteriaBuilder cb = cbf.create(em, String.class) .withReturning(StringIdCTE.class) - .update(TablePerClassBase.class, "t") + .update(TPCBase.class, "t") .setExpression("base", "CONCAT(base, ' - 1')") .returning("id", "base") .end() @@ -434,12 +434,12 @@ public void testTablePerClassReturningInCTESelectOld() { public void work(EntityManager em) { final CriteriaBuilder cb = cbf.create(em, String.class) .withReturning(IdHolderCTE.class) - .update(TablePerClassBase.class, "t") + .update(TPCBase.class, "t") .setExpression("base", "CONCAT(base, ' - 1')") .returning("id", "id") .end() .from(IdHolderCTE.class, "cte") - .fromOld(TablePerClassBase.class, "t") + .fromOld(TPCBase.class, "t") .where("t.id").eqExpression("cte.id") .select("t.base"); @@ -535,12 +535,12 @@ public void testTablePerClassReturningInCTESelectNew() { public void work(EntityManager em) { final CriteriaBuilder cb = cbf.create(em, String.class) .withReturning(IdHolderCTE.class) - .update(TablePerClassBase.class, "t") + .update(TPCBase.class, "t") .setExpression("base", "CONCAT(base, ' - 1')") .returning("id", "id") .end() .from(IdHolderCTE.class, "cte") - .fromNew(TablePerClassBase.class, "t") + .fromNew(TPCBase.class, "t") .where("t.id").eqExpression("cte.id") .select("t.base"); diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/treat/builder/AbstractTreatVariationsTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/treat/builder/AbstractTreatVariationsTest.java index 8a4eee5973..bb9fd897c9 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/treat/builder/AbstractTreatVariationsTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/treat/builder/AbstractTreatVariationsTest.java @@ -109,7 +109,7 @@ public void work(EntityManager em) { JoinedSub1 s1Parent = new JoinedSub1("s1.parent"); JoinedSub2 s2Parent = new JoinedSub2("s2.parent"); - if (strategy.equals("Joined") && supportsJoinedInheritance()) { + if (supportsJoinedInheritance()) { persist(em, i1, s1, s2, s1Parent, s2Parent); } @@ -122,9 +122,7 @@ public void work(EntityManager em) { SingleTableSub1 st1Parent = new SingleTableSub1("st1.parent"); SingleTableSub2 st2Parent = new SingleTableSub2("st2.parent"); - if (strategy.equals("SingleTable")) { - persist(em, i1, st1, st2, st1Parent, st2Parent); - } + persist(em, i1, st1, st2, st1Parent, st2Parent); /**************** * Table per Class @@ -137,7 +135,7 @@ public void work(EntityManager em) { // The Java compiler can't up-cast automatically, maybe a bug? //persist(em, i1, tpc1, tpc2, tpc1Parent, tpc2Parent); - if (strategy.equals("TablePerClass") && supportsTablePerClassInheritance()) { + if (supportsTablePerClassInheritance()) { persist(em, i1, (Sub1) tpc1, (Sub2) tpc2, (Sub1) tpc1Parent, (Sub2) tpc2Parent); } } diff --git a/integration/datanucleus/src/main/java/com/blazebit/persistence/impl/datanucleus/DataNucleusJpaProvider.java b/integration/datanucleus/src/main/java/com/blazebit/persistence/impl/datanucleus/DataNucleusJpaProvider.java index 341178b61f..c2fab88655 100644 --- a/integration/datanucleus/src/main/java/com/blazebit/persistence/impl/datanucleus/DataNucleusJpaProvider.java +++ b/integration/datanucleus/src/main/java/com/blazebit/persistence/impl/datanucleus/DataNucleusJpaProvider.java @@ -134,6 +134,8 @@ public String getCustomFunctionInvocation(String functionName, int argumentCount @Override public boolean supportsRootTreat() { + // Although it might parse, it isn't really supported for JOINED inheritance as wrong SQL is generated + // TODO: create an issue for this return true; } @@ -142,24 +144,40 @@ public boolean supportsTreatJoin() { return major >= 5; } + @Override + public boolean supportsTreatCorrelation() { + return false; + } + @Override public boolean supportsRootTreatJoin() { return false; } + @Override + public boolean supportsRootTreatTreatJoin() { + return false; + } + @Override public boolean supportsSubtypePropertyResolving() { return false; } + @Override + public boolean supportsSubtypeRelationResolving() { + // Interestingly, joining a relation that is only available on a subtype works + return true; + } + @Override public boolean supportsCountStar() { return false; } @Override - public boolean isForeignJoinColumn(ManagedType ownerClass, String attributeName) { - ManagedTypeImpl managedType = (ManagedTypeImpl) ownerClass; + public boolean isForeignJoinColumn(ManagedType ownerType, String attributeName) { + ManagedTypeImpl managedType = (ManagedTypeImpl) ownerType; String[] parts = attributeName.split("\\."); AbstractMemberMetaData metaData = managedType.getMetadata().getMetaDataForMember(parts[0]); for (int i = 1; i < parts.length; i++) { @@ -181,6 +199,11 @@ public boolean isForeignJoinColumn(ManagedType ownerClass, String attributeNa return metaData.getJoinMetaData() != null; } + @Override + public boolean isColumnShared(ManagedType ownerType, String attributeName) { + return false; + } + @Override public boolean isJoinTable(Attribute attribute) { AbstractMemberMetaData metaData = ((AttributeImpl) attribute).getMetadata(); @@ -225,4 +248,9 @@ public boolean needsAssociationToIdRewriteInOnClause() { public boolean needsBrokenAssociationToIdRewriteInOnClause() { return false; } + + @Override + public boolean needsTypeConstraintForColumnSharing() { + return false; + } } diff --git a/integration/eclipselink/src/main/java/com/blazebit/persistence/impl/eclipselink/EclipseLinkJpaProvider.java b/integration/eclipselink/src/main/java/com/blazebit/persistence/impl/eclipselink/EclipseLinkJpaProvider.java index 302d9293d8..7879f99cf0 100644 --- a/integration/eclipselink/src/main/java/com/blazebit/persistence/impl/eclipselink/EclipseLinkJpaProvider.java +++ b/integration/eclipselink/src/main/java/com/blazebit/persistence/impl/eclipselink/EclipseLinkJpaProvider.java @@ -141,9 +141,19 @@ public boolean supportsTreatJoin() { return true; } + @Override + public boolean supportsTreatCorrelation() { + return false; + } + @Override public boolean supportsRootTreatJoin() { - return true; + return false; + } + + @Override + public boolean supportsRootTreatTreatJoin() { + return false; } @Override @@ -151,14 +161,19 @@ public boolean supportsSubtypePropertyResolving() { return false; } + @Override + public boolean supportsSubtypeRelationResolving() { + return false; + } + @Override public boolean supportsCountStar() { return false; } @Override - public boolean isForeignJoinColumn(ManagedType ownerClass, String attributeName) { - ManagedTypeImpl managedType = (ManagedTypeImpl) ownerClass; + public boolean isForeignJoinColumn(ManagedType ownerType, String attributeName) { + ManagedTypeImpl managedType = (ManagedTypeImpl) ownerType; String[] parts = attributeName.split("\\."); DatabaseMapping mapping = managedType.getDescriptor().getMappingForAttributeName(parts[0]); for (int i = 1; i < parts.length; i++) { @@ -170,6 +185,11 @@ public boolean isForeignJoinColumn(ManagedType ownerClass, String attributeNa return false; } + @Override + public boolean isColumnShared(ManagedType ownerType, String attributeName) { + return false; + } + @Override public boolean isJoinTable(Attribute attribute) { DatabaseMapping mapping = ((AttributeImpl) attribute).getMapping(); @@ -223,4 +243,9 @@ public boolean needsBrokenAssociationToIdRewriteInOnClause() { return false; } + @Override + public boolean needsTypeConstraintForColumnSharing() { + return false; + } + } diff --git a/integration/hibernate-base/src/main/java/com/blazebit/persistence/impl/hibernate/HibernateJpaProvider.java b/integration/hibernate-base/src/main/java/com/blazebit/persistence/impl/hibernate/HibernateJpaProvider.java index 70f519323b..90330f7ed9 100644 --- a/integration/hibernate-base/src/main/java/com/blazebit/persistence/impl/hibernate/HibernateJpaProvider.java +++ b/integration/hibernate-base/src/main/java/com/blazebit/persistence/impl/hibernate/HibernateJpaProvider.java @@ -22,6 +22,9 @@ import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; +import org.hibernate.persister.entity.SingleTableEntityPersister; +import org.hibernate.persister.entity.UnionSubclassEntityPersister; +import org.hibernate.type.ComponentType; import org.hibernate.type.ForeignKeyDirection; import org.hibernate.type.OneToOneType; import org.hibernate.type.Type; @@ -30,7 +33,12 @@ import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.ManagedType; import javax.persistence.metamodel.PluralAttribute; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; /** * @@ -42,6 +50,7 @@ public class HibernateJpaProvider implements JpaProvider { private final DB db; private final Map entityPersisters; private final Map collectionPersisters; + private final ConcurrentMap columnSharingCache = new ConcurrentHashMap<>(); private static enum DB { OTHER, @@ -214,24 +223,39 @@ public boolean supportsTreatJoin() { return false; } + @Override + public boolean supportsTreatCorrelation() { + return false; + } + @Override public boolean supportsRootTreatJoin() { return false; } + @Override + public boolean supportsRootTreatTreatJoin() { + return false; + } + @Override public boolean supportsSubtypePropertyResolving() { return true; } + @Override + public boolean supportsSubtypeRelationResolving() { + return true; + } + @Override public boolean supportsCountStar() { return true; } @Override - public boolean isForeignJoinColumn(ManagedType managedType, String attributeName) { - AbstractEntityPersister persister = (AbstractEntityPersister) entityPersisters.get(managedType.getJavaType().getName()); + public boolean isForeignJoinColumn(ManagedType ownerType, String attributeName) { + AbstractEntityPersister persister = (AbstractEntityPersister) entityPersisters.get(ownerType.getJavaType().getName()); Type propertyType = persister.getPropertyType(attributeName); if (propertyType instanceof OneToOneType) { @@ -245,6 +269,67 @@ public boolean isForeignJoinColumn(ManagedType managedType, String attributeN return tableNumber >= persister.getEntityMetamodel().getSubclassEntityNames().size(); } + @Override + public boolean isColumnShared(ManagedType ownerType, String attributeName) { + AbstractEntityPersister persister = (AbstractEntityPersister) entityPersisters.get(ownerType.getJavaType().getName()); + if (!(persister instanceof SingleTableEntityPersister) && !(persister instanceof UnionSubclassEntityPersister)) { + return false; + } + + ColumnSharingCacheKey cacheKey = new ColumnSharingCacheKey(ownerType, attributeName); + Boolean result = columnSharingCache.get(cacheKey); + if (result != null) { + return result; + } + + if (persister instanceof SingleTableEntityPersister) { + SingleTableEntityPersister singleTableEntityPersister = (SingleTableEntityPersister) persister; + SingleTableEntityPersister rootPersister = (SingleTableEntityPersister) entityPersisters.get(singleTableEntityPersister.getRootEntityName()); + result = isColumnShared(singleTableEntityPersister, rootPersister.getName(), rootPersister.getSubclassClosure(), attributeName); + } else if (persister instanceof UnionSubclassEntityPersister) { + UnionSubclassEntityPersister unionSubclassEntityPersister = (UnionSubclassEntityPersister) persister; + UnionSubclassEntityPersister rootPersister = (UnionSubclassEntityPersister) entityPersisters.get(unionSubclassEntityPersister.getRootEntityName()); + result = isColumnShared(unionSubclassEntityPersister, rootPersister.getName(), rootPersister.getSubclassClosure(), attributeName); + } + + columnSharingCache.put(cacheKey, result); + return result; + } + + private boolean isColumnShared(AbstractEntityPersister persister, String rootName, String[] subclassNames, String attributeName) { + String[] columnNames = persister.getSubclassPropertyColumnNames(attributeName); + for (String subclass: subclassNames) { + if (!subclass.equals(persister.getName()) && !subclass.equals(rootName)) { + AbstractEntityPersister subclassPersister = (AbstractEntityPersister) entityPersisters.get(subclass); + if (isColumnShared(subclassPersister, columnNames)) { + return true; + } + } + } + return false; + } + + private boolean isColumnShared(AbstractEntityPersister subclassPersister, String[] columnNames) { + List propertiesToCheck = new ArrayList<>(Arrays.asList(subclassPersister.getPropertyNames())); + while (!propertiesToCheck.isEmpty()) { + String propertyName = propertiesToCheck.remove(propertiesToCheck.size() - 1); + Type propertyType = subclassPersister.getPropertyType(propertyName); + if (propertyType instanceof ComponentType) { + ComponentType componentType = (ComponentType) propertyType; + for (String subPropertyName : componentType.getPropertyNames()) { + propertiesToCheck.add(propertyName + "." + subPropertyName); + } + } else { + String[] subclassColumnNames = subclassPersister.getSubclassPropertyColumnNames(propertyName); + if (Arrays.deepEquals(columnNames, subclassColumnNames)) { + return true; + } + } + } + + return false; + } + @Override public boolean isJoinTable(Attribute attribute) { StringBuilder sb = new StringBuilder(200); @@ -307,4 +392,44 @@ public boolean needsAssociationToIdRewriteInOnClause() { public boolean needsBrokenAssociationToIdRewriteInOnClause() { return true; } + + @Override + public boolean needsTypeConstraintForColumnSharing() { + return true; + } + + private static class ColumnSharingCacheKey { + private final ManagedType managedType; + private final String attributeName; + + public ColumnSharingCacheKey(ManagedType managedType, String attributeName) { + this.managedType = managedType; + this.attributeName = attributeName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ColumnSharingCacheKey that = (ColumnSharingCacheKey) o; + + if (!managedType.equals(that.managedType)) { + return false; + } + return attributeName.equals(that.attributeName); + } + + @Override + public int hashCode() { + int result = managedType.hashCode(); + result = 31 * result + attributeName.hashCode(); + return result; + } + } + } diff --git a/integration/openjpa/src/main/java/com/blazebit/persistence/impl/openjpa/OpenJPAJpaProvider.java b/integration/openjpa/src/main/java/com/blazebit/persistence/impl/openjpa/OpenJPAJpaProvider.java index 21fc3a1570..d8702efb56 100644 --- a/integration/openjpa/src/main/java/com/blazebit/persistence/impl/openjpa/OpenJPAJpaProvider.java +++ b/integration/openjpa/src/main/java/com/blazebit/persistence/impl/openjpa/OpenJPAJpaProvider.java @@ -118,27 +118,47 @@ public boolean supportsTreatJoin() { return false; } + @Override + public boolean supportsTreatCorrelation() { + return false; + } + @Override public boolean supportsRootTreatJoin() { return false; } + @Override + public boolean supportsRootTreatTreatJoin() { + return false; + } + @Override public boolean supportsSubtypePropertyResolving() { return false; } + @Override + public boolean supportsSubtypeRelationResolving() { + return false; + } + @Override public boolean supportsCountStar() { return false; } @Override - public boolean isForeignJoinColumn(ManagedType ownerClass, String attributeName) { + public boolean isForeignJoinColumn(ManagedType ownerType, String attributeName) { // just return true since we don't need that for openjpa anyway return true; } + @Override + public boolean isColumnShared(ManagedType ownerType, String attributeName) { + return false; + } + @Override public boolean isJoinTable(Attribute attribute) { // just return false since we don't need that for openjpa anyway @@ -174,4 +194,9 @@ public boolean needsAssociationToIdRewriteInOnClause() { public boolean needsBrokenAssociationToIdRewriteInOnClause() { return false; } + + @Override + public boolean needsTypeConstraintForColumnSharing() { + return false; + } }