
com.blazebit.persistence.impl.JoinNode Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2014 Blazebit.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.blazebit.persistence.impl;
import java.util.*;
import javax.persistence.Query;
import javax.persistence.metamodel.Attribute;
import com.blazebit.persistence.JoinType;
import com.blazebit.persistence.Root;
import com.blazebit.persistence.impl.expression.*;
import com.blazebit.persistence.impl.predicate.CompoundPredicate;
import com.blazebit.persistence.impl.predicate.EqPredicate;
import com.blazebit.persistence.impl.predicate.Predicate;
/**
*
* @author Christian Beikov
* @author Moritz Becker
* @since 1.0
*/
public class JoinNode implements Root {
private JoinAliasInfo aliasInfo;
private JoinType joinType = JoinType.LEFT;
private boolean fetch = false;
// We need this for count and id queries where we do not need all the joins
private final EnumSet clauseDependencies = EnumSet.noneOf(ClauseType.class);
private final JoinNode parent;
private final JoinTreeNode parentTreeNode;
private final JoinNode correlationParent;
private final String correlationPath;
private final String parentTreatType;
private final Class> propertyClass;
private final String treatType;
private final String valuesFunction;
private final int valueCount;
private final Query valueQuery;
private final String valuesClause;
private final String valuesAliases;
private final Map nodes = new TreeMap(); // Use TreeMap so that joins get applied
// alphabetically for easier testing
private final Set entityJoinNodes = new LinkedHashSet();
// contains other join nodes which this node depends on
private final Set dependencies = new HashSet();
private CompoundPredicate onPredicate;
// Cache
private boolean dirty = true;
private boolean cardinalityMandatory;
public JoinNode(JoinAliasInfo aliasInfo, Class> propertyClass, String valuesFunction, int valueCount, Query valueQuery, String valuesClause, String valuesAliases) {
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.valueQuery = valueQuery;
this.valuesClause = valuesClause;
this.valuesAliases = valuesAliases;
this.correlationParent = null;
this.correlationPath = null;
onUpdate(null);
}
public JoinNode(JoinNode parent, JoinTreeNode parentTreeNode, String parentTreatType, JoinAliasInfo aliasInfo, JoinType joinType, Class> propertyClass, String treatType) {
this.parent = parent;
this.parentTreeNode = parentTreeNode;
this.parentTreatType = parentTreatType;
this.aliasInfo = aliasInfo;
this.joinType = joinType;
this.propertyClass = propertyClass;
this.treatType = treatType;
this.valuesFunction = null;
this.valueCount = 0;
this.valueQuery = null;
this.valuesClause = null;
this.valuesAliases = null;
this.correlationParent = null;
this.correlationPath = null;
onUpdate(null);
}
public JoinNode(JoinNode correlationParent, String correlationPath, String parentTreatType, JoinAliasInfo aliasInfo, Class> propertyClass, String treatType) {
this.parent = null;
this.parentTreeNode = null;
this.joinType = null;
this.correlationParent = correlationParent;
this.correlationPath = correlationPath;
this.parentTreatType = parentTreatType;
this.aliasInfo = aliasInfo;
this.propertyClass = propertyClass;
this.treatType = treatType;
this.valuesFunction = null;
this.valueCount = 0;
this.valueQuery = null;
this.valuesClause = null;
this.valuesAliases = null;
onUpdate(null);
}
private void onUpdate(StateChange stateChange) {
// Once mandatory, only a type change can cause a change of the cardinality mandatory
if (cardinalityMandatory && stateChange != StateChange.JOIN_TYPE) {
return;
}
dirty = true;
if (parent != null) {
parent.onUpdate(StateChange.CHILD);
}
}
public boolean isCardinalityMandatory() {
if (dirty) {
updateCardinalityMandatory();
dirty = false;
}
return cardinalityMandatory;
}
private void updateCardinalityMandatory() {
boolean computedMandatory = false;
if (joinType == JoinType.INNER) {
// If the relation is optional/nullable or the join has a condition
// the join is mandatory, because omitting it might change the semantics of the result set
// NOTE: entity join nodes(the ones which don't have a parentTreeNode) are considered mandatory for now
if (parentTreeNode == null || parentTreeNode.isOptional() || !isEmptyCondition()) {
computedMandatory = true;
}
} else if (joinType == JoinType.LEFT) {
// If the join has a condition which is not an array expression condition
// we definitively need the join
// NOTE: an array expression condition with a left join will always produce 1 row
// so the join is not yet absolutely mandatory
if (!isEmptyCondition() && !isArrayExpressionCondition()) {
computedMandatory = true;
}
// Check if any of the child nodes is mandatory for the cardinality
OUTER: for (Map.Entry nodeEntry : nodes.entrySet()) {
JoinTreeNode treeNode = nodeEntry.getValue();
for (JoinNode childNode : treeNode.getJoinNodes().values()) {
if (childNode.isCardinalityMandatory()) {
computedMandatory = true;
break OUTER;
}
}
}
}
if (computedMandatory != cardinalityMandatory) {
cardinalityMandatory = computedMandatory;
}
}
private boolean isEmptyCondition() {
return onPredicate == null || onPredicate.getChildren().isEmpty();
}
private boolean isArrayExpressionCondition() {
if (onPredicate == null || onPredicate.getChildren().size() != 1) {
return false;
}
Predicate predicate = onPredicate.getChildren().get(0);
if (!(predicate instanceof EqPredicate)) {
return false;
}
EqPredicate eqPredicate = (EqPredicate) predicate;
Expression left = eqPredicate.getLeft();
if (!(left instanceof FunctionExpression)) {
return false;
}
FunctionExpression keyExpression = (FunctionExpression) left;
if (!"KEY".equalsIgnoreCase(keyExpression.getFunctionName()) && !"INDEX".equalsIgnoreCase(keyExpression.getFunctionName())) {
return false;
}
Expression keyContentExpression = keyExpression.getExpressions().get(0);
if (!(keyContentExpression instanceof PathExpression)) {
return false;
}
PathExpression keyPath = (PathExpression) keyContentExpression;
if (!this.equals(keyPath.getBaseNode())) {
return false;
}
return true;
}
public void registerDependencies() {
if (onPredicate != null) {
onPredicate.accept(new VisitorAdapter() {
@Override
public void visit(PathExpression pathExpr) {
// prevent loop dependencies to the same join node
if (pathExpr.getBaseNode() != JoinNode.this && pathExpr.getBaseNode() != null) {
dependencies.add((JoinNode) pathExpr.getBaseNode());
}
}
});
}
}
public void accept(JoinNodeVisitor visitor) {
visitor.visit(this);
for (JoinTreeNode treeNode : nodes.values()) {
for (JoinNode joinNode : treeNode.getJoinNodes().values()) {
joinNode.accept(visitor);
}
}
for (JoinNode joinNode : entityJoinNodes) {
joinNode.accept(visitor);
}
}
public T accept(AbortableResultJoinNodeVisitor visitor) {
T result = visitor.visit(this);
if (visitor.getStopValue().equals(result)) {
return result;
}
for (JoinTreeNode treeNode : nodes.values()) {
for (JoinNode joinNode : treeNode.getJoinNodes().values()) {
result = joinNode.accept(visitor);
if (visitor.getStopValue().equals(result)) {
return result;
}
}
}
for (JoinNode joinNode : entityJoinNodes) {
result = joinNode.accept(visitor);
if (visitor.getStopValue().equals(result)) {
return result;
}
}
return result;
}
public EnumSet getClauseDependencies() {
return clauseDependencies;
}
public JoinTreeNode getParentTreeNode() {
return parentTreeNode;
}
public JoinNode getParent() {
return parent;
}
public JoinAliasInfo getAliasInfo() {
return aliasInfo;
}
public void setAliasInfo(JoinAliasInfo aliasInfo) {
this.aliasInfo = aliasInfo;
}
public JoinType getJoinType() {
return joinType;
}
public void setJoinType(JoinType joinType) {
this.joinType = joinType;
onUpdate(StateChange.JOIN_TYPE);
}
public boolean isFetch() {
return fetch;
}
public void setFetch(boolean fetch) {
this.fetch = fetch;
}
public Map getNodes() {
return nodes;
}
public JoinTreeNode getOrCreateTreeNode(String joinRelationName, Attribute, ?> attribute) {
JoinTreeNode node = nodes.get(joinRelationName);
if (node == null) {
node = new JoinTreeNode(joinRelationName, attribute);
nodes.put(joinRelationName, node);
}
return node;
}
public Set getEntityJoinNodes() {
return entityJoinNodes;
}
public void addEntityJoin(JoinNode entityJoinNode) {
entityJoinNodes.add(entityJoinNode);
}
public String getParentTreatType() {
return parentTreatType;
}
public Class> getPropertyClass() {
return propertyClass;
}
public String getTreatType() {
return treatType;
}
public int getValueCount() {
return valueCount;
}
public Query getValueQuery() {
return valueQuery;
}
public String getValuesClause() {
return valuesClause;
}
public String getValuesAliases() {
return valuesAliases;
}
public JoinNode getCorrelationParent() {
return correlationParent;
}
public String getCorrelationPath() {
return correlationPath;
}
public CompoundPredicate getOnPredicate() {
return onPredicate;
}
public void setOnPredicate(CompoundPredicate onPredicate) {
this.onPredicate = onPredicate;
onUpdate(StateChange.ON_PREDICATE);
}
public Set getDependencies() {
return dependencies;
}
public boolean hasCollections() {
if (!entityJoinNodes.isEmpty()) {
return true;
}
List stack = new ArrayList();
stack.addAll(nodes.values());
for (JoinNode node : entityJoinNodes) {
stack.addAll(node.getNodes().values());
}
while (!stack.isEmpty()) {
JoinTreeNode treeNode = stack.remove(stack.size() - 1);
if (treeNode.isCollection()) {
return true;
}
for (JoinNode joinNode : treeNode.getJoinNodes().values()) {
stack.addAll(joinNode.nodes.values());
}
}
return false;
}
Set getCollectionJoins() {
Set collectionJoins = new HashSet();
List stack = new ArrayList();
stack.addAll(nodes.values());
// TODO: Fix this with #216
// For now we say entity joins are also collection joins because that affects size to count transformations
for (JoinNode node : entityJoinNodes) {
stack.addAll(node.getNodes().values());
}
collectionJoins.addAll(entityJoinNodes);
while (!stack.isEmpty()) {
JoinTreeNode treeNode = stack.remove(stack.size() - 1);
if (treeNode.isCollection()) {
collectionJoins.addAll(treeNode.getJoinNodes().values());
}
for (JoinNode joinNode : treeNode.getJoinNodes().values()) {
stack.addAll(joinNode.nodes.values());
}
}
return collectionJoins;
}
private static enum StateChange {
JOIN_TYPE,
ON_PREDICATE,
CHILD;
}
public void appendAlias(StringBuilder sb, String property) {
if (valuesFunction != null) {
// NOTE: property should always be null
sb.append(valuesFunction).append('(');
sb.append(aliasInfo.getAlias());
sb.append(".value");
sb.append(')');
} else {
sb.append(aliasInfo.getAlias());
if (property != null) {
sb.append('.').append(property);
}
}
}
/* Implementation of Root interface */
@Override
public String getAlias() {
return aliasInfo.getAlias();
}
@Override
public Class> getType() {
return propertyClass;
}
public int getJoinDepth() {
int i = 0;
JoinNode joinNode = this;
while ((joinNode = joinNode.getParent()) != null) {
i++;
}
return i;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy