use of com.blazebit.persistence.JoinType in project blaze-persistence by Blazebit.
the class BlazeCriteriaBuilderRenderer method renderJoins.
private <X extends FromBuilder<X>> X renderJoins(QueryMetadata metadata, FromBaseBuilder<X> fromBuilder) {
X criteriaBuilder = null;
for (final JoinExpression joinExpression : metadata.getJoins()) {
boolean fetch = joinExpression.hasFlag(JPAQueryMixin.FETCH);
boolean hasCondition = joinExpression.getCondition() != null;
Expression<?> target = joinExpression.getTarget();
String alias = null;
if (target instanceof Operation<?>) {
Operation<?> operation = (Operation<?>) target;
if (operation.getOperator() == Ops.ALIAS) {
target = operation.getArg(0);
alias = ((Path<?>) operation.getArg(1)).getMetadata().getName();
}
}
if (target instanceof ValuesExpression<?>) {
ValuesExpression<?> valuesExpression = (ValuesExpression<?>) target;
Class type = valuesExpression.getRoot().getType();
String name = valuesExpression.getAlias().getMetadata().getName();
Collection<?> elements = valuesExpression.getElements();
if (!valuesExpression.getMetadata().isRoot()) {
String attribute = relativePathString(valuesExpression.getRoot(), valuesExpression);
if (valuesExpression.isIdentifiable()) {
criteriaBuilder = (X) fromBuilder.fromIdentifiableValues(type, attribute, name, elements);
} else {
criteriaBuilder = (X) fromBuilder.fromValues(type, attribute, name, elements);
}
} else if (valuesExpression.isIdentifiable()) {
criteriaBuilder = (X) fromBuilder.fromIdentifiableValues(type, name, elements);
} else {
criteriaBuilder = (X) fromBuilder.fromValues(type, name, elements);
}
} else if (target instanceof Path<?>) {
Path<?> entityPath = (Path<?>) target;
boolean entityJoin = entityPath.getMetadata().isRoot();
String renderedExpression = renderExpression(entityPath);
// While this looks suspicious, this is actually in line with Querydsl's default behaviour in JPQLSerializer.handleJoinTarget
if (alias == null && entityJoin) {
alias = renderedExpression;
}
switch(joinExpression.getType()) {
case DEFAULT:
if (fromBuilder instanceof FromBuilder) {
criteriaBuilder = (X) fromBuilder;
From from = criteriaBuilder.getFrom(alias);
// TODO find a clean way to detect FROM clauses that are already set by fromEntitySubquery...
if (from != null) {
if (entityPath instanceof CollectionExpressionBase<?, ?> && ((CollectionExpressionBase<?, ?>) entityPath).getElementType().equals(from.getJavaType()) || from.getJavaType().equals(entityPath.getType())) {
break;
}
}
}
if (entityJoin) {
criteriaBuilder = fromBuilder.from(entityPath.getType(), alias);
} else {
if (fromBuilder instanceof BaseSubqueryBuilder) {
criteriaBuilder = (X) ((BaseSubqueryBuilder<?>) fromBuilder).from(renderedExpression, alias);
} else if (fromBuilder instanceof SubqueryInitiator<?>) {
criteriaBuilder = (X) ((SubqueryInitiator<?>) fromBuilder).from(renderedExpression, alias);
} else {
throw new IllegalArgumentException(renderedExpression + " join not supported here");
}
}
break;
default:
JoinType joinType = getJoinType(joinExpression);
if (hasCondition && fetch) {
LOG.warning("Fetch is ignored due to on-clause");
}
if (entityJoin) {
if (!hasCondition) {
throw new IllegalStateException("No on-clause for entity join!");
}
final JoinOnBuilder<X> xJoinOnBuilder = criteriaBuilder.joinOn(entityPath.getType(), alias, joinType);
setExpressionSubqueries(joinExpression.getCondition(), null, xJoinOnBuilder, JoinOnBuilderExpressionSetter.INSTANCE);
} else if (!hasCondition) {
// If there is no alias, assume a default join
boolean defaultJoin = alias == null || joinExpression.hasFlag(AbstractBlazeJPAQuery.DEFAULT);
if (fetch) {
if (defaultJoin) {
((FullQueryBuilder<?, ?>) criteriaBuilder).joinDefault(renderedExpression, alias, joinType, fetch);
} else {
((FullQueryBuilder<?, ?>) criteriaBuilder).join(renderedExpression, alias, joinType, fetch);
}
} else {
if (defaultJoin) {
criteriaBuilder.joinDefault(renderedExpression, alias, joinType);
} else {
criteriaBuilder.join(renderedExpression, alias, joinType);
}
}
} else {
if (alias == null) {
throw new IllegalArgumentException("This association join requires an alias, like so: .join(" + renderedExpression + ", " + entityPath.getClass().getSimpleName() + "." + entityPath.getMetadata().getName() + ")");
}
final JoinOnBuilder<X> xJoinOnBuilder = criteriaBuilder.joinOn(renderedExpression, alias, joinType);
setExpressionSubqueries(joinExpression.getCondition(), null, xJoinOnBuilder, JoinOnBuilderExpressionSetter.INSTANCE);
}
break;
}
} else if (target instanceof SubQueryExpression) {
switch(joinExpression.getType()) {
case DEFAULT:
{
FullSelectCTECriteriaBuilder<X> xFullSelectCTECriteriaBuilder = fromBuilder.fromSubquery(target.getType(), alias);
Object o = serializeSubQuery(xFullSelectCTECriteriaBuilder, target);
criteriaBuilder = o instanceof FinalSetOperationCTECriteriaBuilder ? ((FinalSetOperationCTECriteriaBuilder<X>) o).end() : ((FullSelectCTECriteriaBuilder<X>) o).end();
break;
}
default:
{
JoinType joinType = getJoinType(joinExpression);
boolean isLateral = joinExpression.hasFlag(AbstractBlazeJPAQuery.LATERAL);
if (fetch) {
LOG.warning("Fetch is ignored due to subquery entity join!");
}
SubQueryExpression<?> subQueryExpression = target.accept(new FirstSubqueryResolver(), null);
Path<?> fromPath = subQueryExpression.getMetadata().getJoins().get(0).getTarget().accept(new FirstSubqueryTargetPathResolver(), null);
boolean entityJoin = fromPath.getMetadata().isRoot();
if (hasCondition) {
FullSelectCTECriteriaBuilder<JoinOnBuilder<X>> joinOnBuilderFullSelectCTECriteriaBuilder;
if (isLateral) {
String subqueryAlias = subQueryExpression.getMetadata().getJoins().get(0).getTarget().accept(new JoinTargetAliasPathResolver(), null).getMetadata().getName();
if (entityJoin) {
joinOnBuilderFullSelectCTECriteriaBuilder = criteriaBuilder.joinLateralOnSubquery(target.getType(), alias, joinType);
} else {
joinOnBuilderFullSelectCTECriteriaBuilder = criteriaBuilder.joinLateralOnSubquery(renderExpression(fromPath), alias, subqueryAlias, joinType);
}
} else {
if (!entityJoin) {
throw new IllegalStateException("Entity join to association");
}
joinOnBuilderFullSelectCTECriteriaBuilder = criteriaBuilder.joinOnSubquery(target.getType(), alias, joinType);
}
Object o = serializeSubQuery(joinOnBuilderFullSelectCTECriteriaBuilder, target);
final JoinOnBuilder<X> joinOnBuilder = o instanceof FinalSetOperationCTECriteriaBuilder ? ((FinalSetOperationCTECriteriaBuilder<JoinOnBuilder<X>>) o).end() : ((FullSelectCTECriteriaBuilder<JoinOnBuilder<X>>) o).end();
criteriaBuilder = (X) setExpressionSubqueries(joinExpression.getCondition(), null, joinOnBuilder, JoinOnBuilderExpressionSetter.INSTANCE);
} else {
if (isLateral) {
FullSelectCTECriteriaBuilder<X> xFullSelectCTECriteriaBuilder;
String subqueryAlias = subQueryExpression.getMetadata().getJoins().get(0).getTarget().accept(new JoinTargetAliasPathResolver(), null).getMetadata().getName();
if (entityJoin) {
xFullSelectCTECriteriaBuilder = criteriaBuilder.joinLateralSubquery(target.getType(), alias, joinType);
} else {
xFullSelectCTECriteriaBuilder = criteriaBuilder.joinLateralSubquery(renderExpression(fromPath), alias, subqueryAlias, joinType);
}
Object o = serializeSubQuery(xFullSelectCTECriteriaBuilder, target);
criteriaBuilder = o instanceof FinalSetOperationCTECriteriaBuilder ? ((FinalSetOperationCTECriteriaBuilder<X>) o).end() : ((FullSelectCTECriteriaBuilder<X>) o).end();
} else {
throw new IllegalStateException("No on-clause for subquery entity join!");
}
}
break;
}
}
} else {
throw new UnsupportedOperationException("Joins for " + target + " is not yet implemented");
}
}
return criteriaBuilder;
}
use of com.blazebit.persistence.JoinType in project blaze-persistence by Blazebit.
the class HibernateJpaProvider method requiresTreatFilter.
@Override
public ConstraintType requiresTreatFilter(EntityType<?> ownerType, String attributeName, JoinType joinType) {
// When we don't support treat joins(Hibernate 4.2), we need to put the constraints into the WHERE clause
if (!supportsTreatJoin() && joinType == JoinType.INNER) {
return ConstraintType.WHERE;
}
AbstractEntityPersister persister = getEntityPersister(ownerType);
Type propertyType = getPropertyType(persister, attributeName);
if (!(propertyType instanceof AssociationType)) {
return ConstraintType.NONE;
}
// When the inner treat joined element is collection, we always need the constraint, see HHH-??? TODO: report issue
if (propertyType instanceof CollectionType) {
if (joinType == JoinType.INNER) {
if (isForeignKeyDirectionToParent((CollectionType) propertyType)) {
return ConstraintType.WHERE;
}
return ConstraintType.ON;
} else if (!((CollectionType) propertyType).getElementType(persister.getFactory()).isEntityType()) {
return ConstraintType.NONE;
}
}
String propertyEntityName = ((AssociationType) propertyType).getAssociatedEntityName(persister.getFactory());
AbstractEntityPersister propertyTypePersister = (AbstractEntityPersister) entityPersisters.get(propertyEntityName);
// When the treat joined element is a union subclass, we always need the constraint, see HHH-??? TODO: report issue
if (propertyTypePersister instanceof UnionSubclassEntityPersister) {
return ConstraintType.ON;
}
return ConstraintType.NONE;
}
use of com.blazebit.persistence.JoinType in project blaze-persistence by Blazebit.
the class AbstractCommonQueryBuilder method joinOnForLateralEmulation.
private JoinOnBuilder<BuilderType> joinOnForLateralEmulation(String correlationPath, String alias, JoinType type) {
Expression expr = expressionFactory.createJoinPathExpression(correlationPath);
JoinNode baseNode = null;
String baseNodeAlias = null;
if (expr instanceof PathExpression) {
PathElementExpression firstElem = ExpressionUtils.getLeftMostPathExpression((PathExpression) expr).getExpressions().get(0);
AliasInfo startAlias;
if (firstElem instanceof ArrayExpression) {
startAlias = null;
} else if (firstElem instanceof PropertyExpression) {
startAlias = aliasManager.getAliasInfo(((PropertyExpression) firstElem).getProperty());
} else {
throw new IllegalArgumentException("Unexpected expression type[" + firstElem.getClass().getSimpleName() + "] in expression: " + correlationPath);
}
if (startAlias instanceof JoinAliasInfo) {
baseNodeAlias = startAlias.getAlias();
baseNode = ((JoinAliasInfo) startAlias).getJoinNode();
}
} else {
throw new IllegalArgumentException("Unexpected expression type[" + expr.getClass().getSimpleName() + "] in expression: " + correlationPath);
}
if (baseNode == null) {
baseNode = getRoot();
}
PathTargetResolvingExpressionVisitor visitor = new PathTargetResolvingExpressionVisitor(getMetamodel(), baseNode.getType(), baseNodeAlias);
expr.accept(visitor);
Map<Attribute<?, ?>, Type<?>> possibleTargets = visitor.getPossibleTargets();
Type<?> t;
if (possibleTargets.size() == 1 && (t = possibleTargets.values().iterator().next()) instanceof EntityType<?>) {
return joinOn((EntityType<?>) t, alias, type);
}
return joinOn(expr, alias, type);
}
use of com.blazebit.persistence.JoinType in project blaze-persistence by Blazebit.
the class JoinManager method implicitJoin.
@SuppressWarnings("checkstyle:methodlength")
public void implicitJoin(Expression expression, boolean joinAllowed, boolean singularJoinAllowed, boolean objectLeafAllowed, String targetTypeName, ClauseType fromClause, JoinType joinType, JoinNode currentJoinNode, Set<String> currentlyResolvingAliases, boolean fromSubquery, boolean fromSelectAlias, boolean joinRequired, boolean idRemovable, boolean fetch, boolean reuseExisting) {
PathExpression pathExpression;
if (expression instanceof PathExpression) {
pathExpression = (PathExpression) expression;
List<PathElementExpression> pathElements = pathExpression.getExpressions();
int pathElementSize = pathElements.size();
PathElementExpression elementExpr = pathElements.get(pathElements.size() - 1);
int singleValuedAssociationNameStartIndex = -1;
int singleValuedAssociationNameEndIndex = -1;
JoinNode current = null;
List<String> resultFields = new ArrayList<>();
JoinResult currentResult;
JoinNode possibleRoot;
int startIndex = 0;
Expression aliasedExpression;
String alias;
// If joinable select alias, it is guaranteed to have only a single element
if (pathExpression.getExpressions().size() == 1 && currentlyResolvingAliases != null && !currentlyResolvingAliases.contains(alias = pathExpression.toString()) && (aliasedExpression = getJoinableSelectAlias(pathExpression, fromClause == ClauseType.SELECT, fromSubquery)) != null) {
// this check is necessary to prevent infinite recursion in the case of e.g. SELECT name AS name
if (!fromSelectAlias) {
try {
currentlyResolvingAliases.add(alias);
// we have to do this implicit join because we might have to adjust the selectOnly flag in the referenced join nodes
implicitJoin(aliasedExpression, joinAllowed, singularJoinAllowed, true, null, fromClause, currentlyResolvingAliases, fromSubquery, true, joinRequired, false);
} finally {
currentlyResolvingAliases.remove(alias);
}
}
return;
} else if (isExternal(pathExpression)) {
// try to correlate the path expression and use the correlation alias here instead
String correlatedAlias = addRoot(null, pathExpression, null, false, true);
if (correlatedAlias != null) {
pathElements.clear();
pathElements.addAll(expressionFactory.createPathExpression(correlatedAlias).getExpressions());
pathElementSize = pathElements.size();
elementExpr = pathExpression.getExpressions().get(pathExpression.getExpressions().size() - 1);
}
PathElementExpression firstElement = pathElements.get(0);
if (firstElement instanceof PropertyExpression) {
AliasInfo aliasInfo = aliasManager.getAliasInfo(((PropertyExpression) firstElement).getProperty());
if (pathElements.size() == 1) {
JoinManager manager;
if (aliasInfo.getAliasOwner() == aliasManager) {
manager = this;
} else {
manager = parent;
}
manager.implicitJoin(pathExpression, true, true, true, targetTypeName, fromClause, currentlyResolvingAliases, true, fromSelectAlias, joinRequired, false);
return;
} else {
current = ((JoinAliasInfo) aliasInfo).getJoinNode();
startIndex = 1;
}
} else if (firstElement instanceof TreatExpression) {
current = implicitJoinTreatExpression((TreatExpression) firstElement, true, true, fromClause, JoinType.LEFT, null, currentlyResolvingAliases, fromSubquery, fromSelectAlias, true, false, false, true);
startIndex = 1;
if (pathElements.size() == 1) {
return;
}
} else {
throw new IllegalArgumentException("Unsupported correlation with expression: " + pathExpression);
}
}
// Skip root speculation if this is just a single element path
if (current == null && pathElements.size() > 1 && (possibleRoot = getRootNode(pathElements.get(0))) != null) {
startIndex = 1;
current = possibleRoot;
}
if (pathElements.size() > startIndex + 1) {
currentResult = implicitJoin(current, pathExpression, fromClause, joinType, currentJoinNode, currentlyResolvingAliases, startIndex, pathElements.size() - 1, false, joinAllowed, singularJoinAllowed, idRemovable);
current = currentResult.baseNode;
resultFields = currentResult.addToList(resultFields);
// It can never be a single valued association id reference if the join type is INNER i.e. it is required
singleValuedAssociationNameStartIndex = currentResult.singleValuedAssociationNameIndex;
singleValuedAssociationNameEndIndex = currentResult.singleValuedAssociationNameEndIndex;
if (singleValuedAssociationNameStartIndex != -1) {
if (!mainQuery.jpaProvider.supportsSingleValuedAssociationIdExpressions()) {
if (idRemovable) {
// remove the id part only if we come from a predicate
elementExpr = null;
if (current == null) {
// This is the case when we use a join alias like "alias.id"
// We need to resolve the base since it might not be the root node
AliasInfo a = aliasManager.getAliasInfo(pathElements.get(currentResult.singleValuedAssociationNameIndex).toString());
// We know this can only be a join node alias
current = ((JoinAliasInfo) a).getJoinNode();
resultFields = Collections.emptyList();
}
} else {
// Need a normal join
elementExpr = null;
resultFields.clear();
currentResult = implicitJoin(current, resultFields, pathExpression, fromClause, joinType, currentJoinNode, currentlyResolvingAliases, currentResult.singleValuedAssociationNameIndex, pathElements.size(), false, joinAllowed, singularJoinAllowed, idRemovable);
current = currentResult.baseNode;
resultFields = currentResult.addToList(resultFields);
singleValuedAssociationNameStartIndex = -1;
}
}
}
} else {
// Single element expression like "alias", "relation", "property" or "alias.relation"
currentResult = implicitJoin(current, pathExpression, fromClause, joinType, currentJoinNode, currentlyResolvingAliases, startIndex, pathElements.size() - 1, false, joinAllowed, singularJoinAllowed, idRemovable);
current = currentResult.baseNode;
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 (isSingleValuedAssociationId(current.getNodeType(), elementExpr)) {
// We remove the "id" part
elementExpr = null;
// Treat it like a single valued association id expression
singleValuedAssociationNameStartIndex = singleValuedAssociationNameEndIndex = startIndex - 1;
}
} else {
// There is no base node, this is a expression with 1 element
// Either relative or a direct alias
String elementExpressionString;
if (elementExpr instanceof ArrayExpression) {
elementExpressionString = ((ArrayExpression) elementExpr).getBase().toString();
} else {
elementExpressionString = elementExpr.toString();
}
AliasInfo a = aliasManager.getAliasInfo(elementExpressionString);
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 (isSingleValuedAssociationId(current.getNodeType(), elementExpr)) {
// We replace the "id" part with the alias
elementExpr = new PropertyExpression(current.getAlias());
}
}
}
}
}
JoinResult result;
AliasInfo aliasInfo;
// The case of a simple join alias usage
if (pathElements.size() == 1 && !fromSelectAlias && currentlyResolvingAliases != null && !currentlyResolvingAliases.contains(alias = elementExpr.toString()) && (aliasInfo = aliasManager.getAliasInfoForBottomLevel(alias)) != null) {
// No need to assert the resultFields here since they can't appear anyways if we enter this branch
if (aliasInfo instanceof SelectInfo) {
if (targetTypeName != null) {
throw new IllegalArgumentException("The select alias '" + aliasInfo.getAlias() + "' can not be used for a treat expression!.");
}
// We actually allow usage of select aliases in expressions, but JPA doesn't, so we have to resolve them here
Expression selectExpr = ((SelectInfo) aliasInfo).getExpression();
if (!(selectExpr instanceof PathExpression)) {
throw new RuntimeException("The select expression '" + selectExpr.toString() + "' is not a simple path expression! No idea how to implicit join that.");
}
// join the expression behind a select alias once when it is encountered the first time
if (((PathExpression) selectExpr).getBaseNode() == null) {
implicitJoin(selectExpr, joinAllowed, singularJoinAllowed, objectLeafAllowed, null, fromClause, currentlyResolvingAliases, fromSubquery, true, joinRequired, false);
}
PathExpression selectPathExpr = (PathExpression) selectExpr;
PathReference reference = selectPathExpr.getPathReference();
result = new JoinResult((JoinNode) selectPathExpr.getBaseNode(), Arrays.asList(selectPathExpr.getField()), reference.getType(), -1, -1);
} else {
JoinNode pathJoinNode = ((JoinAliasInfo) aliasInfo).getJoinNode();
if (targetTypeName != null) {
// Treated root path
ManagedType<?> targetType = metamodel.managedType(targetTypeName);
result = new JoinResult(pathJoinNode);
} else {
// Naked join alias usage like in "KEY(joinAlias)"
result = new JoinResult(pathJoinNode);
}
}
} else if (pathElements.size() == 1 && elementExpr instanceof QualifiedExpression) {
QualifiedExpression qualifiedExpression = (QualifiedExpression) elementExpr;
JoinNode baseNode;
if (elementExpr instanceof MapKeyExpression) {
baseNode = joinMapKey((MapKeyExpression) elementExpr, null, fromClause, currentlyResolvingAliases, fromSubquery, fromSelectAlias, true, fetch, true, true);
} else if (elementExpr instanceof ListIndexExpression) {
baseNode = joinListIndex((ListIndexExpression) elementExpr, null, fromClause, currentlyResolvingAliases, fromSubquery, fromSelectAlias, true, fetch, true, true);
} else if (elementExpr instanceof MapEntryExpression) {
baseNode = joinMapEntry((MapEntryExpression) elementExpr, null, fromClause, currentlyResolvingAliases, fromSubquery, fromSelectAlias, true, fetch, true, true);
} else if (elementExpr instanceof MapValueExpression) {
implicitJoin(qualifiedExpression.getPath(), true, singularJoinAllowed, objectLeafAllowed, targetTypeName, fromClause, joinType, null, currentlyResolvingAliases, fromSubquery, fromSelectAlias, joinRequired, false, fetch, false);
baseNode = (JoinNode) qualifiedExpression.getPath().getBaseNode();
} else {
throw new IllegalArgumentException("Unknown qualified expression type: " + elementExpr);
}
result = new JoinResult(baseNode);
} else {
if (singleValuedAssociationNameStartIndex != -1) {
String associationName = new PathExpression(pathElements.subList(singleValuedAssociationNameStartIndex, singleValuedAssociationNameEndIndex + 1)).toString();
AliasInfo singleValuedAssociationRootAliasInfo = null;
JoinTreeNode treeNode;
// } else
if (pathElements.size() == 2) {
// If this path is composed of only two elements, the association name could represent an alias
singleValuedAssociationRootAliasInfo = aliasManager.getAliasInfoForBottomLevel(associationName);
}
if (singleValuedAssociationRootAliasInfo != null) {
JoinNode singleValuedAssociationRoot = ((JoinAliasInfo) singleValuedAssociationRootAliasInfo).getJoinNode();
if (elementExpr != null) {
AttributeHolder attributeHolder = JpaUtils.getAttributeForJoining(metamodel, singleValuedAssociationRoot.getNodeType(), elementExpr, null);
Type<?> type = attributeHolder.getAttributeType();
result = new JoinResult(singleValuedAssociationRoot, Arrays.asList(elementExpr.toString()), type, -1, -1);
} else {
result = new JoinResult(singleValuedAssociationRoot);
}
} else {
if (current == null) {
current = getRootNodeOrFail("Could not join path [", expression, "] because it did not use an absolute path but multiple root nodes are available!");
}
treeNode = current.getNodes().get(associationName);
if (reuseExisting && treeNode != null && treeNode.getDefaultNode() != null) {
if (elementExpr != null) {
Expression restExpression = new PathExpression(pathElements.subList(singleValuedAssociationNameEndIndex + 1, pathElementSize));
String elementString = restExpression.toString();
AttributeHolder attributeHolder = JpaUtils.getAttributeForJoining(metamodel, treeNode.getDefaultNode().getNodeType(), restExpression, null);
Type<?> type = attributeHolder.getAttributeType();
result = new JoinResult(treeNode.getDefaultNode(), Arrays.asList(elementString), type, -1, -1);
} else {
result = new JoinResult(treeNode.getDefaultNode());
}
} else {
if (elementExpr != null) {
Expression restExpression = new PathExpression(pathElements.subList(singleValuedAssociationNameStartIndex, pathElementSize));
String elementString = restExpression.toString();
AttributeHolder attributeHolder = JpaUtils.getAttributeForJoining(metamodel, currentResult.baseNode.getNodeType(), restExpression, null);
Type<?> type = attributeHolder.getAttributeType();
result = new JoinResult(currentResult.baseNode, Arrays.asList(elementString), type, -1, -1);
} else if (metamodel.getManagedType(ExtendedManagedType.class, current.getManagedType()).getAttributes().get(associationName) != null) {
Expression resultExpr = new PathExpression(new PropertyExpression(associationName));
AttributeHolder attributeHolder = JpaUtils.getAttributeForJoining(metamodel, current.getNodeType(), resultExpr, null);
Type<?> type = attributeHolder.getAttributeType();
result = new JoinResult(current, Arrays.asList(associationName), type, -1, -1);
} else {
result = new JoinResult(current);
}
}
}
} else if (elementExpr instanceof ArrayExpression) {
// Element collection case
ArrayExpression arrayExpr = (ArrayExpression) elementExpr;
if (arrayExpr.getBase() instanceof PropertyExpression) {
if (current == null) {
current = getRootNodeOrFail("Could not join path [", expression, "] because it did not use an absolute path but multiple root nodes are available!");
}
}
String joinRelationName = arrayExpr.getBase().toString();
implicitJoinIndex(arrayExpr);
// Find a node by a predicate match
JoinNode matchingNode;
if (pathElements.size() == 1 && (aliasInfo = aliasManager.getAliasInfoForBottomLevel(joinRelationName)) != null) {
// The first node is allowed to be a join alias
if (aliasInfo instanceof SelectInfo) {
throw new IllegalArgumentException("Illegal reference to the select alias '" + joinRelationName + "'");
}
current = ((JoinAliasInfo) aliasInfo).getJoinNode();
generateAndApplyOnPredicate(current, arrayExpr);
} else if ((matchingNode = findNode(current, joinRelationName, arrayExpr)) != null) {
// We found a join node for the same join relation with the same array expression predicate
current = matchingNode;
} else {
String joinAlias = getJoinAlias(arrayExpr);
if (arrayExpr.getBase() instanceof PropertyExpression) {
resultFields.add(joinRelationName);
currentResult = createOrUpdateNode(current, resultFields, targetTypeName, joinAlias, joinType, currentJoinNode, true, false, joinAllowed, singularJoinAllowed);
} else {
joinAlias = aliasManager.generateJoinAlias(joinAlias);
Class<?> entityClass = ((EntityLiteral) arrayExpr.getBase()).getValue();
joinOn(null, rootNodes.get(0).getAlias(), entityClass, joinAlias, JoinType.LEFT, false).end();
currentResult = new JoinResult(((JoinAliasInfo) aliasManager.getAliasInfo(joinAlias)).getJoinNode());
}
current = currentResult.baseNode;
// TODO: Not sure if necessary
if (currentResult.hasField()) {
throw new IllegalArgumentException("The join path [" + pathExpression + "] has a non joinable part [" + currentResult.joinFields() + "]");
}
generateAndApplyOnPredicate(current, arrayExpr);
}
result = new JoinResult(current);
} else if (!pathExpression.isUsedInCollectionFunction()) {
if (current == null) {
current = getRootNodeOrFail("Could not join path [", expression, "] because it did not use an absolute path but multiple root nodes are available!");
}
if (resultFields.isEmpty()) {
result = implicitJoinSingle(current, elementExpr.toString(), targetTypeName, joinType, currentJoinNode, objectLeafAllowed, joinRequired, joinAllowed, singularJoinAllowed);
} else {
resultFields.add(elementExpr.toString());
String attributeName = StringUtils.join(".", resultFields);
// Validates and gets the path type
getPathType(current.getNodeType(), attributeName, pathExpression);
result = implicitJoinSingle(current, attributeName, targetTypeName, joinType, currentJoinNode, objectLeafAllowed, joinRequired, joinAllowed, singularJoinAllowed);
}
} else {
if (current == null) {
current = getRootNodeOrFail("Could not join path [", expression, "] because it did not use an absolute path but multiple root nodes are available!");
}
if (resultFields.isEmpty()) {
String attributeName = elementExpr.toString();
Type<?> type = getPathType(current.getNodeType(), attributeName, pathExpression);
result = new JoinResult(current, Arrays.asList(attributeName), type, -1, -1);
} else {
resultFields.add(elementExpr.toString());
String attributeName = StringUtils.join(".", resultFields);
Type<?> type = getPathType(current.getNodeType(), attributeName, pathExpression);
result = new JoinResult(current, resultFields, type, -1, -1);
}
}
}
if (fetch) {
fetchPath(result.baseNode);
}
// Don't forget to update the clause dependencies, but only for normal attribute accesses, that way paginated queries can prevent joins in certain cases
if (fromClause != null) {
try {
result.baseNode.updateClauseDependencies(fromClause, new LinkedHashSet<JoinNode>());
} catch (IllegalStateException ex) {
throw new IllegalArgumentException("Implicit join in expression '" + expression + "' introduces cyclic join dependency!", ex);
}
}
if (result.isLazy()) {
pathExpression.setPathReference(new LazyPathReference(result.baseNode, result.joinFields(), result.type, joinAllowed));
} else {
pathExpression.setPathReference(new SimplePathReference(result.baseNode, result.joinFields(), result.type));
}
} else if (expression instanceof FunctionExpression) {
FunctionExpression functionExpression = (FunctionExpression) expression;
List<Expression> expressions = functionExpression.getExpressions();
int size = expressions.size();
for (int i = 0; i < size; i++) {
implicitJoin(expressions.get(i), joinAllowed, singularJoinAllowed, objectLeafAllowed, null, fromClause, currentlyResolvingAliases, fromSubquery, fromSelectAlias, joinRequired, false);
}
List<OrderByItem> withinGroup = functionExpression.getWithinGroup();
if (withinGroup != null) {
size = withinGroup.size();
for (int i = 0; i < size; i++) {
implicitJoin(withinGroup.get(i).getExpression(), joinAllowed, singularJoinAllowed, objectLeafAllowed, null, fromClause, currentlyResolvingAliases, fromSubquery, fromSelectAlias, joinRequired, false);
}
}
} else if (expression instanceof MapKeyExpression) {
MapKeyExpression mapKeyExpression = (MapKeyExpression) expression;
joinMapKey(mapKeyExpression, null, fromClause, currentlyResolvingAliases, fromSubquery, fromSelectAlias, joinRequired, fetch, true, true);
} else if (expression instanceof QualifiedExpression) {
implicitJoin(((QualifiedExpression) expression).getPath(), joinAllowed, singularJoinAllowed, objectLeafAllowed, null, fromClause, currentlyResolvingAliases, fromSubquery, fromSelectAlias, joinRequired, false);
} else if (expression instanceof ArrayExpression || expression instanceof GeneralCaseExpression || expression instanceof TreatExpression) {
// NOTE: I haven't found a use case for this yet, so I'd like to throw an exception instead of silently not supporting this
throw new IllegalArgumentException("Unsupported expression for implicit joining found: " + expression);
} else {
// Other expressions don't need handling
}
}
Aggregations