diff --git a/core/api/src/main/java/com/blazebit/persistence/spi/JpaProvider.java b/core/api/src/main/java/com/blazebit/persistence/spi/JpaProvider.java
index 9a001d9e94..668cb60152 100644
--- a/core/api/src/main/java/com/blazebit/persistence/spi/JpaProvider.java
+++ b/core/api/src/main/java/com/blazebit/persistence/spi/JpaProvider.java
@@ -107,7 +107,6 @@ public interface JpaProvider {
* Whether dereferencing a VALUE function expression is supported by the JPA provider.
*
* @return True if dereferencing is supported, false otherwise
- * @since 1.2.0
*/
public boolean supportsCollectionValueDereference();
@@ -156,6 +155,7 @@ public interface JpaProvider {
/**
* Whether treating a from/root alias is supported.
+ * For example SELECT TREAT(alias AS Subtype).property FROM ..
*
* @return True if treating a from alias is supported, false otherwise
*/
@@ -163,17 +163,35 @@ public interface JpaProvider {
/**
* Whether a treat join is supported.
+ * For example SELECT ... FROM .. JOIN TREAT(alias.relation AS Subtype)
*
* @return True if a treat join is supported, false otherwise
*/
public boolean supportsTreatJoin();
+ /**
+ * Whether a correlation path with a treat expression is supported.
+ * For example SELECT (SELECT .. FROM TREAT(parent AS Subtype).relation) FROM ..
+ *
+ * @return True if a treat in correlation expressions is supported, false otherwise
+ */
+ public boolean supportsTreatCorrelation();
+
+ /**
+ * Whether a root treat in a join is supported.
+ * For example SELECT ... FROM .. JOIN TREAT(alias AS Subtype).relation
+ *
+ * @return True if a root treat in a join is supported, false otherwise
+ */
+ public boolean supportsRootTreatJoin();
+
/**
* Whether a root treat in a treat join is supported.
+ * For example SELECT ... FROM .. JOIN TREAT(TREAT(alias AS Subtype).relation AS Subtype)
*
* @return True if a root treat in a treat join is supported, false otherwise
*/
- public boolean supportsRootTreatJoin();
+ public boolean supportsRootTreatTreatJoin();
/**
* Whether properties accessed of a from node are implicitly resolved to properties of a subtype of the from node.
@@ -182,6 +200,13 @@ public interface JpaProvider {
*/
public boolean supportsSubtypePropertyResolving();
+ /**
+ * Whether relations of a from node in joins are implicitly resolved to the relations of a subtype of the from node.
+ *
+ * @return True if subtype relation resolving is supported, false otherwise
+ */
+ public boolean supportsSubtypeRelationResolving();
+
/**
* Whether the COUNT(*)
syntax is supported.
*
@@ -192,11 +217,21 @@ public interface JpaProvider {
/**
* Whether the join columns for the given attribute are in a foreign table.
*
- * @param ownerClass The owner of the attribute
+ * @param ownerType The owner of the attribute
* @param attributeName The attribute name to check
* @return True if join columns are in a foreign table, false otherwise
*/
- public boolean isForeignJoinColumn(ManagedType> ownerClass, String attributeName);
+ public boolean isForeignJoinColumn(ManagedType> ownerType, String attributeName);
+
+ /**
+ * Whether columns for the given attribute are shared between multiple subtypes
+ * or shared by occupying the same slot in the resulting SQL.
+ *
+ * @param ownerType The owner of the attribute
+ * @param attributeName The attribute name to check
+ * @return True if columns of the attribute are shared, false otherwise
+ */
+ public boolean isColumnShared(ManagedType> ownerType, String attributeName);
/**
* Whether the given attribute is a collection that uses a join table.
@@ -235,7 +270,6 @@ public interface JpaProvider {
* The value is not yet used but will be in a future version. Also see: https://github.com/Blazebit/blaze-persistence/issues/402
*
* @return true if supported, else false
- * @since 1.2.0
*/
public boolean supportsForeignAssociationInOnClause();
@@ -244,7 +278,6 @@ public interface JpaProvider {
* Indicates if the provider supports the use of transient entity objects as parameters.
*
* @return true if supported, else false
- * @since 1.2.0
*/
public boolean supportsTransientEntityAsParameter();
@@ -253,8 +286,7 @@ public interface JpaProvider {
* If needed, an expression like alias.association
in the ON clause is rewritten to
* alias.association.id
.
*
- * @return true if supported, else false
- * @since 1.2.0
+ * @return true if required, else false
*/
public boolean needsAssociationToIdRewriteInOnClause();
@@ -263,8 +295,16 @@ public interface JpaProvider {
* If needed, an expression like alias.association
in the ON clause is rewritten to
* alias.association.id
which relies on a broken type check in older Hibernate versions.
*
- * @return true if supported, else false
- * @since 1.2.0
+ * @return true if required, else false
*/
public boolean needsBrokenAssociationToIdRewriteInOnClause();
+
+ /**
+ * Indicates if the provider does column sharing for same named columns in inheritance mappings
+ * and thus requires the use of a CASE WHEN expression for restricting casted accesses like e.g. TREAT(alias AS Subtype).property
+ * to retain cast semantics.
+ *
+ * @return true if required, else false
+ */
+ public boolean needsTypeConstraintForColumnSharing();
}
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