com.sap.cds.impl.AssociationAnalyzer Maven / Gradle / Ivy
/*******************************************************************
* © 2019 SAP SE or an SAP affiliate company. All rights reserved. *
*******************************************************************/
package com.sap.cds.impl;
import static com.sap.cds.impl.builder.model.ComparisonPredicate.eq;
import static com.sap.cds.impl.builder.model.Connectors.and;
import static com.sap.cds.impl.builder.model.ElementRefImpl.element;
import static com.sap.cds.ql.impl.ExpressionVisitor.copy;
import static java.util.Comparator.comparing;
import java.util.Optional;
import com.sap.cds.impl.builder.model.Connectors;
import com.sap.cds.ql.ElementRef;
import com.sap.cds.ql.Predicate;
import com.sap.cds.ql.Value;
import com.sap.cds.ql.cqn.CqnComparisonPredicate.Operator;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnModifier;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnReference;
import com.sap.cds.ql.cqn.CqnSyntaxException;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.ql.impl.ExpressionVisitor;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
public class AssociationAnalyzer {
public CqnPredicate getOnCondition(CdsElement association) {
CdsAssociationType assoc = association.getType();
Optional on = assoc.onCondition();
if (on.isPresent()) {
return resolveOnCondition(on.get(), association);
}
return managedOn(association);
}
private CqnPredicate resolveOnCondition(CqnPredicate on, CdsElement association) {
on = copy(on, new SelfResolver(association));
return on;
}
private static CqnPredicate managedOn(CdsElement association) {
return association.getType().as(CdsAssociationType.class).keys().sorted(comparing(CdsElement::getName))
.map(key -> eqForward(association.getName(), key, key.getName())).collect(and());
}
private static Predicate eqForward(String assoc, CdsElement key, String keyName) {
if (key.getType().isAssociation()) {
return key.getType().as(CdsAssociationType.class).keys()
.map(k -> eqForward(assoc, k, fkName(keyName, k.getName()))).collect(Connectors.and());
}
CqnValue lhs = element(fkName(assoc, keyName));
CqnValue rhs = element(assoc, keyName);
return eq(lhs, rhs);
}
private static String fkName(String association, String element) {
return association + "_" + element;
}
private final class SelfResolver implements CqnModifier {
private final CdsElement association;
private SelfResolver(CdsElement association) {
this.association = association;
}
@Override
public Predicate comparison(Value> lhs, Operator op, Value> rhs) {
if (isSelfRef(lhs)) {
assertEqRef(op, rhs);
return self(association, rhs.asRef());
} else if (isSelfRef(rhs)) {
assertEqRef(op, lhs);
return self(association, lhs.asRef());
}
return CqnModifier.super.comparison(lhs, op, rhs);
}
private void assertEqRef(Operator op, CqnValue val) {
if (op != Operator.EQ || !val.isRef()) {
throw new CqnSyntaxException("$self must be compared with '=' ref");
}
}
private boolean isSelfRef(CqnValue value) {
if (!value.isRef()) {
return false;
}
return isSelfRef(value.asRef());
}
private boolean isSelfRef(CqnElementRef ref) {
if (ref.segments().size() == 1) {
return "$self".equals(ref.firstSegment());
}
return false;
}
private Predicate self(CdsElement association, CqnReference backlink) {
// TODO add support for multiple path segments
// to allow for structured types containing associations
String assocName = association.getName();
if (!backlink.firstSegment().equals(assocName) || backlink.segments().size() != 2) {
throw new UnsupportedOperationException("Unsupported on condition in association "
+ association.getDeclaringType() + "." + association);
}
CdsEntity entity = association.getDeclaringType();
CdsEntity assocTarget = entity.getTargetOf(assocName);
String backlinkName = backlink.segments().get(1).id();
CdsElement backLinkAssoc = assocTarget.getAssociation(backlinkName);
Optional backlinkOn = backLinkAssoc.getType().as(CdsAssociationType.class).onCondition();
if (backlinkOn.isPresent()) {
return backlinkOn(assocName, backlinkName, backlinkOn.get());
}
return backLinkAssoc.getType().as(CdsAssociationType.class).keys().sorted(comparing(CdsElement::getName))
.map(key -> eqReverse(assocName, backlinkName, key, key.getName())).collect(and());
}
private Predicate backlinkOn(String assocName, String backlinkName, CqnPredicate on) {
return ExpressionVisitor.copy(on, new CqnModifier() {
@Override
public Value> ref(ElementRef> ref) {
if (ref.firstSegment().equals(backlinkName)) {
return element(ref.lastSegment());
} else {
return element(assocName, ref.firstSegment());
}
}
});
}
private Predicate eqReverse(String assoc, String backlinkName, CdsElement key, String keyName) {
if (key.getType().isAssociation()) {
return key.getType().as(CdsAssociationType.class).keys()
.map(k -> eqReverse(assoc, backlinkName, k, fkName(keyName, k.getName()))).collect(and());
}
CqnValue lhs = element(assoc, fkName(backlinkName, keyName));
CqnValue rhs = element(keyName);
return eq(lhs, rhs);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy