com.sap.cds.ql.impl.CqnAnalyzerImpl Maven / Gradle / Ivy
/*******************************************************************
* © 2019 SAP SE or an SAP affiliate company. All rights reserved. *
*******************************************************************/
package com.sap.cds.ql.impl;
import static com.sap.cds.util.CdsModelUtils.entity;
import static com.sap.cds.util.CqnStatementUtils.resolveStar;
import static com.sap.cds.util.CqnStatementUtils.selectedRefs;
import static java.util.stream.Collectors.toList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import com.google.common.collect.Maps;
import com.sap.cds.ql.cqn.AnalysisResult;
import com.sap.cds.ql.cqn.CqnAnalyzer.CqnAnalyzerSPI;
import com.sap.cds.ql.cqn.CqnComparisonPredicate;
import com.sap.cds.ql.cqn.CqnConnectivePredicate;
import com.sap.cds.ql.cqn.CqnConnectivePredicate.Operator;
import com.sap.cds.ql.cqn.CqnDelete;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnInPredicate;
import com.sap.cds.ql.cqn.CqnNegation;
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.CqnSelectListValue;
import com.sap.cds.ql.cqn.CqnSource;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnUpdate;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.ql.cqn.CqnVisitor;
import com.sap.cds.ql.cqn.ResolvedRefItem;
import com.sap.cds.ql.cqn.ResolvedSegment;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.util.CdsModelUtils;
public class CqnAnalyzerImpl implements CqnAnalyzerSPI {
@Override
public AnalysisResult analyze(CdsModel model, CqnStructuredTypeRef ref) {
List segments = resolveSegments(model, ref.segments());
return new Result(segments, Optional.empty());
}
@Override
public AnalysisResult analyze(CdsModel model, CqnSelect select) {
List segments = resolveSegments(model, select.ref().segments());
return new Result(segments, select.where());
}
@Override
public AnalysisResult analyze(CdsModel model, CqnUpdate update) {
List segments = resolveSegments(model, update.ref().segments());
return new Result(segments, update.where());
}
@Override
public AnalysisResult analyze(CdsModel model, CqnDelete delete) {
List segments = resolveSegments(model, delete.ref().segments());
return new Result(segments, delete.where());
}
@Override
public Iterable resolveRefItems(CdsModel model, CqnSelect query) {
return resolveRefItems(model, query, i -> true);
}
private static List resolveRefItems(CdsModel model, CqnSelect query,
Predicate filter) {
CqnSource from = query.from();
if (from.isRef()) {
CdsEntity target = entity(model, from.asRef());
query = resolveStar(query, target);
return selectedRefs(query).filter(filter).map(r -> new ResolvedRef(model, target, r)).collect(toList());
}
return Collections.emptyList();
}
private List resolveSegments(CdsModel model, List extends Segment> segments) {
List entities = CdsModelUtils.entities(model, segments);
return IntStream.range(0, segments.size())
.mapToObj(i -> new ResolvedSeg(segments.get(i), entities.get(i), Optional.empty())).collect(toList());
}
public static Map extractKeys(Segment seg, CdsEntity entity) {
Optional filter = seg.filter();
if (filter.isPresent()) {
return new ResolvedSeg(seg, entity, filter).keys();
}
return Collections.emptyMap();
}
private static class ResolvedRef implements ResolvedRefItem {
private final CdsModel model;
private final CdsEntity entity;
private final CqnSelectListValue value;
private ResolvedRef(CdsModel model, CdsEntity entity, CqnSelectListValue value) {
this.model = model;
this.entity = entity;
this.value = value;
}
@Override
public String displayName() {
return value.displayName();
}
@Override
public CqnElementRef ref() {
return value.value().asRef();
}
@Override
public CdsElement element() {
return CdsModelUtils.element(entity, ref());
}
@Override
public Optional origin() {
Optional projection = entity.query();
if (projection.isPresent()) {
List ref = resolveRefItems(model, projection.get(),
i -> i.displayName().equals(ref().lastSegment()));
if (!ref.isEmpty()) {
return Optional.of(ref.get(0));
}
}
return Optional.empty();
}
}
private static class Result implements AnalysisResult {
private final List segments;
private final Optional where;
public Result(List segments, Optional where) {
this.segments = new ArrayList<>(segments);
this.where = where;
}
@Override
public Iterator iterator() {
return segments.iterator();
}
@Override
public Iterator reverse() {
ListIterator li = segments.listIterator(segments.size());
return new Iterator() {
@Override
public boolean hasNext() {
return li.hasPrevious();
}
@Override
public ResolvedSegment next() { // NOSONAR
return li.previous();
}
};
}
@Override
public CdsEntity rootEntity() {
return segments.get(0).entity();
}
@Override
public Map rootKeys() {
return segments.get(0).keys();
}
@Override
public CdsEntity targetEntity() {
return segments.get(segments.size() - 1).entity();
}
@Override
public Map targetKeys() {
return target(ResolvedSegment::keys);
}
@Override
public Map targetKeyValues() {
return target(ResolvedSegment::keyValues);
}
@Override
public Map targetValues() {
return target(ResolvedSegment::values);
}
private Map target(Function> filter) {
ResolvedSegment target = segments.get(segments.size() - 1);
if (where.isPresent()) {
target = new ResolvedSeg(target.segment(), target.entity(), where);
}
return filter.apply(target);
}
}
private static class ResolvedSeg implements ResolvedSegment {
private final Segment segment;
private final CdsEntity entity;
private final List filters = new ArrayList<>(1);
private ResolvedSeg(Segment segment, CdsEntity entity, Optional where) {
this.segment = segment;
this.entity = entity;
segment.filter().ifPresent(filters::add);
where.ifPresent(filters::add);
}
@Override
public Segment segment() {
return segment;
}
@Override
public CdsEntity entity() {
return entity;
}
@Override
public Map values() {
Map values = new HashMap<>();
ValueExtractor valExtractor = ValueExtractor.addValues(values);
filters.forEach(f -> f.accept(valExtractor));
return values;
}
@Override
public Map keys() {
return keyValues(true);
}
@Override
public Map keyValues() {
return keyValues(false);
}
private Map keyValues(boolean emptyToNull) {
Set keyNames = CdsModelUtils.keyNames(entity);
Map values = values();
if (values.containsKey(CqnElementRef.$KEY)) {
String key = keyNames.iterator().next();
values.put(key, values.remove(CqnElementRef.$KEY));
}
if (emptyToNull) {
keyNames.forEach(k -> values.putIfAbsent(k, null));
}
return new HashMap<>(Maps.filterKeys(values, keyNames::contains));
}
}
private static final class ValueExtractor implements CqnVisitor {
private final Map values;
private boolean ambiguous;
private boolean add;
private ValueExtractor(Map values, boolean add) {
this.values = values;
this.add = add;
}
static ValueExtractor addValues(Map values) {
return new ValueExtractor(values, true);
}
static ValueExtractor removeValues(Map values) {
return new ValueExtractor(values, false);
}
@Override
public void visit(CqnComparisonPredicate predicate) {
if (predicate.operator() == CqnComparisonPredicate.Operator.EQ
|| predicate.operator() == CqnComparisonPredicate.Operator.IS) {
CqnValue left = predicate.left();
CqnValue right = predicate.right();
if (left.isRef()) {
addKey((CqnElementRef) left, right);
} else if (right.isRef()) {
addKey((CqnElementRef) right, left);
}
}
}
@Override
public void visit(CqnInPredicate in) {
CqnValue val = in.value();
if (val.isRef()) {
in.values().forEach(v -> addKey(val.asRef(), v));
}
}
@Override
public void visit(CqnConnectivePredicate con) {
if (con.operator() == Operator.OR) {
clearValues();
}
}
@Override
public void visit(CqnNegation neg) {
neg.predicate().accept(ValueExtractor.removeValues(values));
}
private void clearValues() {
values.clear();
}
private void addKey(CqnElementRef element, CqnValue value) {
if (element.segments().size() == 1) {
String name = element.displayName();
if (!ambiguous) {
if (add) {
Object val = value(value);
Object oldVal = values.put(name, val);
if (oldVal != null && !Objects.equals(oldVal, val)) {
ambiguous = true;
clearValues();
}
} else {
values.remove(name);
}
}
}
}
private Object value(CqnValue value) {
if (value.isLiteral()) {
return value.asLiteral().value();
}
if (value.isNullValue()) {
return null;
}
return value;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy