com.sap.cds.util.ProjectionUtils Maven / Gradle / Ivy
/*******************************************************************
* © 2020 SAP SE or an SAP affiliate company. All rights reserved. *
*******************************************************************/
package com.sap.cds.util;
import static com.sap.cds.impl.builder.model.ElementRefImpl.parse;
import static com.sap.cds.ql.CQL.copy;
import static com.sap.cds.util.CqnStatementUtils.getEntries;
import static com.sap.cds.util.CqnStatementUtils.setEntries;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import com.sap.cds.ql.CQL;
import com.sap.cds.ql.ElementRef;
import com.sap.cds.ql.StructuredTypeRef;
import com.sap.cds.ql.Value;
import com.sap.cds.ql.cqn.AnalysisResult;
import com.sap.cds.ql.cqn.CqnAnalyzer;
import com.sap.cds.ql.cqn.CqnModifier;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnReference.Segment;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnSelectListItem;
import com.sap.cds.ql.cqn.CqnStatement;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.ql.cqn.ResolvedSegment;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsModel;
public class ProjectionUtils {
private ProjectionUtils() {
// empty
}
private static boolean isSupportedProjection(CqnSelect projection) {
return !projection.having().isPresent() && projection.groupBy().isEmpty() && !projection.where().isPresent()
&& !(projection.from().isJoin()) && !projection.isDistinct();
}
/**
* Resolves the statement against all underlying views, as long as they are
* views supported by projection resolving
*
* @param the CQN statement type
* @param statement the CQN statement
* @param model the CDS model
* @return the resolved statement (as far as supported)
*/
public static T resolveAll(T statement, CdsModel model) {
T previous;
do {
previous = statement;
statement = resolve(previous, model);
} while (previous != statement);
return statement;
}
/**
* Resolves the statement against the views defined by the statements entity
* path. The statement is resolved if all views along the entity path are
* supported by projection resolving.
*
* If the statement can't be resolved, the original statement is returned.
*
* @param the CQN statement type
* @param statement the CQN statement
* @param model the CDS model
* @return the resolved statement, or the original statement, if the projection
* could not be resolved.
*/
public static T resolve(T statement, CdsModel model) {
if (statement.isSelect()) {
return statement;
}
CqnAnalyzer analyzer = CqnAnalyzer.create(model);
CqnStructuredTypeRef ref = statement.ref();
AnalysisResult analysis = analyzer.analyze(ref);
Iterator segmentIterator = analysis.iterator();
if (ref.segments().isEmpty()) {
throw new IllegalStateException("statement has no path at all");
}
T resolved = statement;
// resolve projection of root entity
CdsEntity root = segmentIterator.next().entity();
Optional query = root.query();
if (query.isPresent()) {
CqnSelect projection = query.get();
if (!isSupportedProjection(projection) || hasContextVariable(root, statement)) {
return statement;
}
String projectionTarget = projection.ref().firstSegment();
// TODO this also needs to adapt potential filters
resolved = changeTarget(statement, projectionTarget);
}
// process remaining entity path
CdsEntity previous = root;
while (segmentIterator.hasNext()) {
CdsEntity entity = segmentIterator.next().entity();
query = entity.query();
if (query.isPresent()) {
CqnSelect projection = query.get();
// TODO this also needs to adapt element names and potential filters
if (!isSupportedProjection(projection)) {
return statement;
}
} else if (previous.query().isPresent()) {
// can't consistently resolve a single layer
return statement;
}
previous = entity;
}
// resolve projection of target
if (query.isPresent()) {
CqnSelect projection = query.get();
CdsEntity entity = analyzer.analyze(projection).targetEntity();
List items = projection.items();
ProjectionResolver p = new ProjectionResolver(entity, items);
if (items.isEmpty() || items.size() == 1 && items.get(0).isStar()) {
return resolved;
} else if (!p.isSupportedProjection()) {
return statement;
}
// process aliases in select list and where
resolved = p.resolve(resolved);
// TODO look at columns, where, orderby, etc. based on the projection of the
// target based on that we need to handle renames
}
return resolved;
}
private static boolean hasContextVariable(CdsEntity entity, T statement) {
// TODO: Support cds.valid.from & cds.valid.to annotation conditions in a view.
return !(((CqnStatement) statement).isInsert()) && entity.elements().anyMatch(
e -> e.findAnnotation("cds.valid.from").isPresent() || e.findAnnotation("cds.valid.to").isPresent());
}
/**
* Returns a new statement with the target changed to {@code newTarget}. In a
* path expression the first segment is changed.
*
* @param s the statement
* @param newTarget the new target
* @return the modified statement
*/
public static T changeTarget(T s, String newTarget) {
return copy(s, new CqnModifier() {
@Override
public CqnStructuredTypeRef ref(StructuredTypeRef ref) {
ref.rootSegment().id(newTarget);
return ref;
}
});
}
private static class ProjectionResolver {
private Map displayNameToRef = new HashMap<>();
public ProjectionResolver(CdsEntity entity, List slis) {
slis.stream().filter(CqnSelectListItem::isValue).map(CqnSelectListItem::asValue).forEach(slv -> {
CqnValue val = slv.value();
if (val.isRef() && CdsModelUtils.findElement(entity, val.asRef())
.map(e -> e.getType().isAssociation() && CdsModelUtils.isSingleValued(e.getType()))
.orElse(true)) {
// add mapping for flattened (4odata) structured elements & associated foreign
// key elements
String prefix = val.asRef().displayName() + "_";
entity.elements().filter(e -> e.getName().startsWith(prefix)).forEach(e -> {
String elementInStruct = e.getName().substring(prefix.length() - 1);
displayNameToRef.put(slv.displayName() + elementInStruct, CQL.get(e.getName()));
});
} else {
displayNameToRef.put(slv.displayName(), val);
}
});
}
public T resolve(T statement) {
setEntries(statement, resolveEntries(getEntries(statement)));
return resolveRefs(statement);
}
public List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy