Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.blazebit.persistence.impl.FunctionalDependencyAnalyzerVisitor Maven / Gradle / Ivy
/*
* Copyright 2014 - 2020 Blazebit.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.blazebit.persistence.impl;
import com.blazebit.persistence.parser.EntityMetamodel;
import com.blazebit.persistence.parser.expression.ArithmeticExpression;
import com.blazebit.persistence.parser.expression.ArrayExpression;
import com.blazebit.persistence.parser.expression.DateLiteral;
import com.blazebit.persistence.parser.expression.EntityLiteral;
import com.blazebit.persistence.parser.expression.EnumLiteral;
import com.blazebit.persistence.parser.expression.Expression;
import com.blazebit.persistence.parser.expression.FunctionExpression;
import com.blazebit.persistence.parser.expression.GeneralCaseExpression;
import com.blazebit.persistence.parser.expression.ListIndexExpression;
import com.blazebit.persistence.parser.expression.MapEntryExpression;
import com.blazebit.persistence.parser.expression.MapKeyExpression;
import com.blazebit.persistence.parser.expression.MapValueExpression;
import com.blazebit.persistence.parser.expression.NullExpression;
import com.blazebit.persistence.parser.expression.NumericLiteral;
import com.blazebit.persistence.parser.expression.ParameterExpression;
import com.blazebit.persistence.parser.expression.PathExpression;
import com.blazebit.persistence.parser.expression.PathReference;
import com.blazebit.persistence.parser.expression.SimpleCaseExpression;
import com.blazebit.persistence.parser.expression.StringLiteral;
import com.blazebit.persistence.parser.expression.SubqueryExpression;
import com.blazebit.persistence.parser.expression.TimeLiteral;
import com.blazebit.persistence.parser.expression.TimestampLiteral;
import com.blazebit.persistence.parser.expression.TrimExpression;
import com.blazebit.persistence.parser.expression.TypeFunctionExpression;
import com.blazebit.persistence.parser.expression.WhenClauseExpression;
import com.blazebit.persistence.parser.predicate.BooleanLiteral;
import com.blazebit.persistence.parser.predicate.CompoundPredicate;
import com.blazebit.persistence.parser.util.JpaMetamodelUtils;
import com.blazebit.persistence.spi.ExtendedAttribute;
import com.blazebit.persistence.spi.ExtendedManagedType;
import com.blazebit.persistence.spi.JpaProvider;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EmbeddableType;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*
* @author Christian Beikov
* @since 1.3.0
*/
class FunctionalDependencyAnalyzerVisitor extends EmbeddableSplittingVisitor {
private final ConstantifiedJoinNodeAttributeCollector constantifiedJoinNodeAttributeCollector;
private final Map> uniquenessMissingJoinNodeAttributes;
private final Map> uniquenessFormingJoinNodeExpressions;
private final Map> functionalDependencyRootExpressions;
private Object lastJoinNode;
private ResolvedExpression currentResolvedExpression;
private boolean resultUnique;
private boolean inKey;
public FunctionalDependencyAnalyzerVisitor(EntityMetamodel metamodel, SplittingVisitor splittingVisitor, JpaProvider jpaProvider, AliasManager aliasManager) {
super(metamodel, jpaProvider, aliasManager, splittingVisitor);
this.constantifiedJoinNodeAttributeCollector = new ConstantifiedJoinNodeAttributeCollector(metamodel, aliasManager);
this.uniquenessMissingJoinNodeAttributes = new HashMap<>();
this.uniquenessFormingJoinNodeExpressions = new HashMap<>();
this.functionalDependencyRootExpressions = new LinkedHashMap<>();
}
public void reset() {
constantifiedJoinNodeAttributeCollector.reset();
}
public void clear(CompoundPredicate rootPredicate, JoinNode firstRootNode, boolean innerJoin) {
super.clear();
uniquenessMissingJoinNodeAttributes.clear();
uniquenessFormingJoinNodeExpressions.clear();
functionalDependencyRootExpressions.clear();
resultUnique = false;
lastJoinNode = null;
constantifiedJoinNodeAttributeCollector.collectConstantifiedJoinNodeAttributes(rootPredicate, firstRootNode, innerJoin);
}
public ConstantifiedJoinNodeAttributeCollector getConstantifiedJoinNodeAttributeCollector() {
return constantifiedJoinNodeAttributeCollector;
}
public boolean analyzeFormsUniqueTuple(Expression expression) {
lastJoinNode = null;
expressionToSplit = null;
boolean unique = expression.accept(this);
JoinNode p;
if (unique) {
if (lastJoinNode instanceof Map.Entry) {
p = ((Map.Entry) lastJoinNode).getKey();
} else {
p = (JoinNode) lastJoinNode;
}
if (p != null) {
// Traverse join nodes up and if we see that this expression is part of a collection, we revert result uniqueness
while (p.getParent() != null) {
if ((p.getParentTreeNode() == null || p.getParentTreeNode().isCollection()) && !constantifiedJoinNodeAttributeCollector.isConstantified(p)) {
unique = false;
break;
}
p = p.getParent();
}
}
}
resultUnique = resultUnique || unique;
collectSplittedOffExpressions(expression);
return unique;
}
public ResolvedExpression[] getFunctionalDependencyRootExpressions(CompoundPredicate rootPredicate, ResolvedExpression[] expressions, JoinNode firstRootNode) {
clear(rootPredicate, firstRootNode, true);
for (int i = 0; i < expressions.length; i++) {
currentResolvedExpression = expressions[i];
if (analyzeFormsUniqueTuple(expressions[i].getExpression())) {
List resolvedExpressions = uniquenessFormingJoinNodeExpressions.get(lastJoinNode);
if (resolvedExpressions != null) {
functionalDependencyRootExpressions.put(lastJoinNode, resolvedExpressions);
lastJoinNode = null;
}
}
}
currentResolvedExpression = null;
lastJoinNode = null;
if (functionalDependencyRootExpressions.isEmpty()) {
return null;
}
List resolvedExpressions;
if (functionalDependencyRootExpressions.size() == 1) {
resolvedExpressions = functionalDependencyRootExpressions.values().iterator().next();
} else {
resolvedExpressions = new ArrayList<>();
OUTER: for (Map.Entry> entry : functionalDependencyRootExpressions.entrySet()) {
JoinNode node;
if (entry.getKey() instanceof Map.Entry) {
node = ((Map.Entry) entry.getKey()).getKey();
} else {
node = (JoinNode) entry.getKey();
}
// We always need collections in here
if (node.getParentTreeNode() == null || node.getParentTreeNode().isCollection()) {
// Skip *ToOne join nodes that are functionally dependent on an existing node
if (entry.getKey() instanceof Map.Entry && functionalDependencyRootExpressions.containsKey(node)) {
continue OUTER;
}
resolvedExpressions.addAll(entry.getValue());
} else {
// Skip *ToOne join nodes that are functionally dependent on an existing node
while (node.getParent() != null) {
if (functionalDependencyRootExpressions.containsKey(node.getParent())) {
continue OUTER;
}
node = node.getParent();
}
resolvedExpressions.addAll(entry.getValue());
}
}
}
return resolvedExpressions.toArray(new ResolvedExpression[resolvedExpressions.size()]);
}
public boolean isResultUnique() {
return resultUnique;
}
@Override
public Boolean visit(PathExpression expr) {
PathReference pathReference = expr.getPathReference();
if (pathReference == null) {
Expression aliasedExpression = ((SelectInfo) aliasManager.getAliasInfo(expr.toString())).getExpression();
return aliasedExpression.accept(this);
}
JoinNode baseNode = (JoinNode) pathReference.getBaseNode();
String field = pathReference.getField();
if (field == null) {
lastJoinNode = baseNode;
functionalDependencyRootExpressions.put(baseNode, Collections.singletonList(currentResolvedExpression));
// This is a basic element collection. The element is it's unique key
if (baseNode.getType().getPersistenceType() == Type.PersistenceType.BASIC) {
return true;
}
// The key of a collection is it's unique key
if (inKey) {
return true;
}
field = baseNode.getParentTreeNode().getRelationName();
baseNode = baseNode.getParent();
}
// First we check if the target attribute is unique, if it isn't, we don't need to check the join structure
ExtendedManagedType managedType = metamodel.getManagedType(ExtendedManagedType.class, baseNode.getManagedType());
Attribute attr = managedType.getAttribute(field).getAttribute();
if (attr instanceof PluralAttribute) {
lastJoinNode = baseNode;
if (inKey) {
registerFunctionalDependencyRootExpression(baseNode);
return true;
}
throw new IllegalArgumentException("Ordering by plural attribute '" + expr + "' does not make sense! Please order by it's id instead!");
}
// Right now we only support ids, but we actually should check for unique constraints
boolean isEmbeddedIdPart = false;
SingularAttribute singularAttr = (SingularAttribute) attr;
if (!singularAttr.isId() && !(isEmbeddedIdPart = isEmbeddedIdPart(baseNode, field, singularAttr))) {
registerFunctionalDependencyRootExpression(baseNode);
return false;
}
Object baseNodeKey;
// Check if we have a single valued id access
int dotIndex = expr.getField().lastIndexOf('.');
if (dotIndex == -1) {
baseNodeKey = baseNode;
if (singularAttr.getType() instanceof EmbeddableType) {
expressionToSplit = expr;
}
} else if (isEmbeddedIdPart) {
baseNodeKey = baseNode;
} else {
// We have to correct the base node for single valued id paths
String associationName = expr.getField().substring(0, dotIndex);
ExtendedManagedType extendedManagedType = metamodel.getManagedType(ExtendedManagedType.class, baseNode.getManagedType());
ExtendedAttribute extendedAttribute = extendedManagedType.getAttribute(associationName);
Attribute attribute = extendedAttribute.getAttribute();
baseNodeKey = new AbstractMap.SimpleEntry<>(baseNode, associationName);
if (attribute.getPersistentAttributeType() != Attribute.PersistentAttributeType.ONE_TO_ONE) {
boolean nonConstantParent = true;
Map constantifiedAttributes = constantifiedJoinNodeAttributeCollector.getConstantifiedJoinNodeAttributes().get(baseNode);
if (constantifiedAttributes != null) {
Map orderedAttributes = new HashMap<>();
addAttributes(baseNode.getEntityType(), null, "", "", (SingularAttribute) attribute, orderedAttributes);
initConstantifiedAttributes(orderedAttributes, constantifiedAttributes.keySet());
orderedAttributes.remove(expr.getField());
String singleNonConstantifiedAttribute = getSingleNonConstantifiedAttribute(orderedAttributes);
// If the identifiers are constantified, we don't care if this is a one-to-one
if (singleNonConstantifiedAttribute != null && (singleNonConstantifiedAttribute.isEmpty() || equalsAny(singleNonConstantifiedAttribute, extendedManagedType.getAttribute(expr.getField()).getColumnEquivalentAttributes()))) {
nonConstantParent = false;
orderedAttributes.clear();
managedType = metamodel.getManagedType(ExtendedManagedType.class, JpaMetamodelUtils.resolveFieldClass(baseNode.getJavaType(), attribute));
}
} else if (attribute instanceof SingularAttribute && ((SingularAttribute) attribute).isId()) {
baseNodeKey = baseNode;
// This is an id class attribute
nonConstantParent = false;
// This is not 100% correct as it is not an embedded id, but works because we only need to append the association name field part
isEmbeddedIdPart = true;
}
if (nonConstantParent) {
registerFunctionalDependencyRootExpression(baseNodeKey);
return false;
}
} else {
managedType = metamodel.getManagedType(ExtendedManagedType.class, JpaMetamodelUtils.resolveFieldClass(baseNode.getJavaType(), attribute));
}
}
registerFunctionalDependencyRootExpression(baseNodeKey);
// First we initialize the names of the id attributes as set for the join node
Map orderedAttributes = getUniquenessMissingAttributes(baseNodeKey, managedType);
// We remove for every id attribute from the initialized set of id attribute names
String prefix;
if (dotIndex == -1 && baseNode.getParentTreeNode() != null && !baseNode.getParentTreeNode().isCollection()) {
prefix = baseNode.getParentTreeNode().getRelationName() + ".";
JoinNode node = baseNode.getParent();
while (node.getParentTreeNode() != null) {
prefix = node.getParentTreeNode().getRelationName() + "." + prefix;
node = node.getParent();
}
} else {
prefix = isEmbeddedIdPart ? field.substring(0, dotIndex + 1) : "";
}
if (removeAttribute(prefix, singularAttr, orderedAttributes) && currentResolvedExpression != null) {
List resolvedExpressions = uniquenessFormingJoinNodeExpressions.get(baseNodeKey);
if (resolvedExpressions == null) {
resolvedExpressions = new ArrayList<>(orderedAttributes.size() + 1);
uniquenessFormingJoinNodeExpressions.put(baseNodeKey, resolvedExpressions);
}
resolvedExpressions.add(currentResolvedExpression);
}
lastJoinNode = baseNodeKey;
// While there still are some attribute names left, we simply report that it isn't unique, yet
if (hasNonConstantifiedAttribute(orderedAttributes)) {
return false;
}
// But even now that we order by all id attribute parts, we still have to make sure this join node is uniqueness preserving
String subPath = field;
while (baseNode.getParent() != null) {
if (baseNode.getParentTreeNode() == null) {
// Only assume uniqueness when the encountered cross or entity join are constantified
return constantifiedJoinNodeAttributeCollector.isConstantified(baseNode);
} else {
subPath = baseNode.getParentTreeNode().getRelationName() + "." + subPath;
attr = baseNode.getParentTreeNode().getAttribute();
// Only one-to-one relation joins i.e. joins having a unique key with unique key equality predicate are uniqueness preserving
baseNode = baseNode.getParent();
if (attr.getPersistentAttributeType() != Attribute.PersistentAttributeType.ONE_TO_ONE) {
Map constantifiedAttributes = constantifiedJoinNodeAttributeCollector.getConstantifiedJoinNodeAttributes().get(baseNode);
if (constantifiedAttributes != null) {
// If there are constantified attributes for the node, we check if they cover the identifier
// This is relevant for queries like `select e.manyToOne.id, e.manyToOne.name from Entity e order by e.manyToOne.id`
// Normally, the expression `e.manyToOne.id` wouldn't be considered unique, unless there is a unique key with constant equality predicate
// i.e. a predicate like `e.id = 1` that essentially "constantifies" the parent join node
ExtendedManagedType extendedManagedType = metamodel.getManagedType(ExtendedManagedType.class, baseNode.getManagedType());
orderedAttributes = new HashMap<>();
addAttributes(baseNode.getEntityType(), null, "", "", (SingularAttribute) attr, orderedAttributes);
initConstantifiedAttributes(orderedAttributes, constantifiedAttributes.keySet());
orderedAttributes.remove(subPath);
String singleNonConstantifiedAttribute = getSingleNonConstantifiedAttribute(orderedAttributes);
// If the identifiers are constantified, we don't care if this is a one-to-one
if (singleNonConstantifiedAttribute != null && (singleNonConstantifiedAttribute.isEmpty() || extendedManagedType.getAttributes().containsKey(subPath) && equalsAny(singleNonConstantifiedAttribute, extendedManagedType.getAttribute(subPath).getColumnEquivalentAttributes()))) {
continue;
}
}
return false;
}
}
}
return true;
}
private static boolean hasNonConstantifiedAttribute(Map orderedAttributes) {
for (Map.Entry entry : orderedAttributes.entrySet()) {
if (entry.getValue() == Boolean.FALSE) {
return true;
}
}
return false;
}
private static void initConstantifiedAttributes(Map orderedAttributes, Set constantifiedAttributes) {
for (String constantifiedAttribute : constantifiedAttributes) {
if (orderedAttributes.containsKey(constantifiedAttribute)) {
orderedAttributes.put(constantifiedAttribute, Boolean.TRUE);
}
}
}
private String getSingleNonConstantifiedAttribute(Map orderedAttributes) {
// Null means there are multiple or no constantified attributes
if (orderedAttributes.isEmpty()) {
// The empty string signals no attributes
return "";
}
String attribute = null;
for (Map.Entry entry : orderedAttributes.entrySet()) {
if (entry.getValue() == Boolean.FALSE) {
if (attribute == null) {
attribute = entry.getKey();
} else {
return null;
}
}
}
// Is the case of having only constantified attributes even reasonable?
// if (attribute == null) {
// return "";
// }
return attribute;
}
private boolean equalsAny(String attribute, Set> columnEquivalentAttributes) {
StringBuilder sb = null;
for (ExtendedAttribute columnEquivalentAttribute : columnEquivalentAttributes) {
List> attributePath = columnEquivalentAttribute.getAttributePath();
String attributeName;
if (attributePath.size() == 1) {
attributeName = attributePath.get(0).getName();
} else {
if (sb == null) {
sb = new StringBuilder();
} else {
sb.setLength(0);
}
sb.append(attributePath.get(0).getName());
for (int i = 1; i < attributePath.size(); i++) {
sb.append('.');
sb.append(attributePath.get(i).getName());
}
attributeName = sb.toString();
}
if (attribute.equals(attributeName)) {
return true;
}
}
return false;
}
private Map getUniquenessMissingAttributes(Object baseNodeKey, ExtendedManagedType managedType) {
Map orderedAttributes = uniquenessMissingJoinNodeAttributes.get(baseNodeKey);
if (orderedAttributes == null) {
orderedAttributes = new HashMap<>();
JoinNode baseNode;
EntityType entityType;
if (baseNodeKey instanceof Map.Entry) {
baseNode = (JoinNode) ((Map.Entry) baseNodeKey).getKey();
entityType = baseNode.getEntityType();
} else {
baseNode = (JoinNode) baseNodeKey;
entityType = baseNode.getEntityType();
}
if (baseNode.getParentTreeNode() == null || baseNode.getParentTreeNode().getAttribute().isCollection()) {
for (SingularAttribute idAttribute : managedType.getIdAttributes()) {
addAttributes(entityType, null, "", "", idAttribute, orderedAttributes);
}
} else {
String fieldPrefix = baseNode.getParentTreeNode().getRelationName();
fieldPrefix = fieldPrefix.substring(0, fieldPrefix.length() - baseNode.getParentTreeNode().getAttribute().getName().length());
if (baseNodeKey instanceof Map.Entry) {
addAttributes(entityType, null, fieldPrefix, "", entityType.getSingularAttribute(((Map.Entry) baseNodeKey).getValue()), orderedAttributes);
} else {
addAttributes(baseNode.getParent().getEntityType(), null, fieldPrefix, "", (SingularAttribute) baseNode.getParentTreeNode().getAttribute(), orderedAttributes);
}
}
Map constantifiedAttributes = constantifiedJoinNodeAttributeCollector.getConstantifiedJoinNodeAttributes().get(baseNodeKey);
if (constantifiedAttributes != null) {
initConstantifiedAttributes(orderedAttributes, constantifiedAttributes.keySet());
}
uniquenessMissingJoinNodeAttributes.put(baseNodeKey, orderedAttributes);
}
return orderedAttributes;
}
private boolean removeAttribute(String prefix, SingularAttribute singularAttribute, Map orderedAttributes) {
String attributeName;
if (prefix.isEmpty()) {
attributeName = singularAttribute.getName();
} else {
attributeName = prefix + singularAttribute.getName();
}
if (singularAttribute.getType() instanceof EmbeddableType) {
String newPrefix = attributeName + ".";
boolean removed = false;
for (SingularAttribute attribute : ((EmbeddableType) singularAttribute.getType()).getSingularAttributes()) {
if (removeAttribute(newPrefix, attribute, orderedAttributes)) {
removed = true;
}
}
return removed;
} else {
return orderedAttributes.remove(attributeName) != null;
}
}
private boolean isEmbeddedIdPart(JoinNode baseNode, String field, SingularAttribute attr) {
if (attr.getDeclaringType() instanceof EmbeddableType) {
ManagedType managedType = baseNode.getManagedType();
if (managedType instanceof EntityType) {
int dotIndex = field.indexOf('.');
EntityType entityType = (EntityType) managedType;
SingularAttribute potentialIdAttribute = entityType.getSingularAttribute(field.substring(0, dotIndex));
return potentialIdAttribute.isId();
}
}
return false;
}
private void registerFunctionalDependencyRootExpression(Object baseNode) {
if (currentResolvedExpression != null && !uniquenessFormingJoinNodeExpressions.containsKey(baseNode)) {
List resolvedExpressions = functionalDependencyRootExpressions.get(baseNode);
if (resolvedExpressions == null) {
resolvedExpressions = new ArrayList<>();
functionalDependencyRootExpressions.put(baseNode, resolvedExpressions);
}
resolvedExpressions.add(currentResolvedExpression);
}
}
@Override
public Boolean visit(ListIndexExpression expression) {
boolean oldInKey = inKey;
inKey = true;
try {
return expression.getPath().accept(this);
} finally {
inKey = oldInKey;
}
}
@Override
public Boolean visit(MapKeyExpression expression) {
boolean oldInKey = inKey;
inKey = true;
try {
return expression.getPath().accept(this);
} finally {
inKey = oldInKey;
}
}
@Override
public Boolean visit(NullExpression expression) {
// The actual semantics of NULL are, that NULL != NULL
return true;
}
@Override
public Boolean visit(FunctionExpression expression) {
switch (expression.getFunctionName().toUpperCase()) {
case "COALESCE":
// Can only be sure to produce unique value if the first element is unique
return expression.getExpressions().get(0).accept(this);
case "NULLIF":
// See visit(NullExpression) for reasoning
return expression.getExpressions().get(0).accept(this);
case "ROW_NUMBER":
// ROW_NUMBER is unique when using no partitions
return expression.getWindowDefinition() == null || expression.getWindowDefinition().getFilterPredicate() == null && expression.getWindowDefinition().getPartitionExpressions().isEmpty() && expression.getWindowDefinition().getWindowName() == null;
case "MIN":
case "MAX": {
// MIN and MAX are special aggregate functions that preserve uniqueness
Expression expr = expression.getExpressions().get(0);
return expr instanceof PathExpression && visit((PathExpression) expr);
}
default:
// The existing JPA functions don't return unique results regardless of their arguments
return false;
}
}
/* Other expressions can never be detected to be unique */
@Override
public Boolean visit(ArrayExpression expression) {
return false;
}
@Override
public Boolean visit(GeneralCaseExpression expression) {
return false;
}
@Override
public Boolean visit(SimpleCaseExpression expression) {
return false;
}
@Override
public Boolean visit(WhenClauseExpression expression) {
return false;
}
@Override
public Boolean visit(ParameterExpression expression) {
return false;
}
@Override
public Boolean visit(MapEntryExpression expression) {
return false;
}
@Override
public Boolean visit(MapValueExpression expression) {
return false;
}
@Override
public Boolean visit(SubqueryExpression expression) {
return false;
}
@Override
public Boolean visit(TypeFunctionExpression expression) {
return false;
}
@Override
public Boolean visit(TrimExpression expression) {
return false;
}
@Override
public Boolean visit(ArithmeticExpression expression) {
return false;
}
@Override
public Boolean visit(NumericLiteral expression) {
return false;
}
@Override
public Boolean visit(BooleanLiteral expression) {
return false;
}
@Override
public Boolean visit(StringLiteral expression) {
return false;
}
@Override
public Boolean visit(DateLiteral expression) {
return false;
}
@Override
public Boolean visit(TimeLiteral expression) {
return false;
}
@Override
public Boolean visit(TimestampLiteral expression) {
return false;
}
@Override
public Boolean visit(EnumLiteral expression) {
return false;
}
@Override
public Boolean visit(EntityLiteral expression) {
return false;
}
}