All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cds.ql.impl.CqnAnalyzerImpl Maven / Gradle / Ivy

There is a newer version: 3.6.1
Show newest version
/*******************************************************************
 * © 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 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