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

com.sap.cds.util.PathExpressionResolver Maven / Gradle / Ivy

There is a newer version: 3.8.0
Show newest version
package com.sap.cds.util;

import static com.sap.cds.util.CdsModelUtils.isReverseAssociation;
import static java.util.stream.Collectors.toMap;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import com.sap.cds.CdsException;
import com.sap.cds.SessionContext;
import com.sap.cds.impl.builder.model.Conjunction;
import com.sap.cds.ql.Delete;
import com.sap.cds.ql.Insert;
import com.sap.cds.ql.Update;
import com.sap.cds.ql.Upsert;
import com.sap.cds.ql.cqn.CqnDelete;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnUpdate;
import com.sap.cds.ql.cqn.CqnXsert;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsModel;

public class PathExpressionResolver {

	private final CdsModel cdsModel;
	private final CqnStructuredTypeRef ref;
	private final SessionContext session;

	private PathExpressionResolver(CdsModel cdsModel, SessionContext session, CqnStructuredTypeRef ref) {
		this.cdsModel = cdsModel;
		this.session = session;
		this.ref = ref;
	}

	@SuppressWarnings("unchecked")
	public static  S resolvePath(CdsModel cdsModel, SessionContext session, S statement) {
		PathExpressionResolver per = new PathExpressionResolver(cdsModel, session, statement.ref());
		LinkedList path = per.unfoldPathExpression();

		if (!isPathExpression(path)) {
			if (path.getFirst().expr.isPresent()) {
				throw new UnsupportedOperationException(
						"Infix filters are not supported in Insert/Upsert statements: " + statement);
			}
			return statement;
		}

		List> entries = addValues(statement.entries(), per.getTargetFkValues(parentEntry(path)));
		CdsEntity target = targetEntity(path);
		if (statement.isInsert()) {
			return (S) Insert.into(target).entries(entries);
		}
		if (statement.isUpsert()) {
			return (S) Upsert.into(target).entries(entries);
		}
		throw new IllegalStateException();
	}

	public static CqnUpdate resolvePath(CdsModel cdsModel, SessionContext session, CqnUpdate update) {
		PathExpressionResolver per = new PathExpressionResolver(cdsModel, session, update.ref());
		LinkedList path = per.unfoldPathExpression();

		CqnPredicate wherePredicate;
		if (isPathExpression(path)) {
			wherePredicate = conjunctPredicates(per.getOnConditionPredicate(parentEntry(path)), update.where(),
					path.getLast().expr);
		} else {
			wherePredicate = conjunctPredicates(update.where(), path.getLast().expr);
		}

		return Update.entity(targetEntity(path)).entries(update.entries()).where(wherePredicate);
	}

	// TODO resolvePath adds FK elements not part of CDS model (CDSJAVA-1837)
	public static CqnDelete resolvePath(CdsModel cdsModel, SessionContext session, CqnDelete delete) {
		PathExpressionResolver per = new PathExpressionResolver(cdsModel, session, delete.ref());
		LinkedList path = per.unfoldPathExpression();

		CqnPredicate wherePredicate;
		if (isPathExpression(path)) {
			wherePredicate = conjunctPredicates(per.getOnConditionPredicate(parentEntry(path)), delete.where(),
					path.getLast().expr);
		} else {
			wherePredicate = conjunctPredicates(delete.where(), path.getLast().expr);
		}

		return Delete.from(targetEntity(path)).where(wherePredicate);

	}

	public static List resolvePathCascadeDelete(CdsModel cdsModel, SessionContext session,
			CqnStructuredTypeRef ref, List> entries) {
		PathExpressionResolver per = new PathExpressionResolver(cdsModel, session, ref);
		LinkedList path = per.unfoldPathExpression();

		Map fkValues = isPathExpression(path) ? per.getTargetFkValues(parentEntry(path))
				: Collections.emptyMap();

		return entries.stream()
				.map(e -> createDeleteStatement(cdsModel, targetEntity(path).getQualifiedName(), e, fkValues))
				.collect(Collectors.toList());
	}

	private static CqnPredicate conjunctPredicates(Optional where, Optional filter) {
		return Conjunction.and(where, filter).orElse(null);
	}

	@SafeVarargs
	private static CqnPredicate conjunctPredicates(CqnPredicate predicate, Optional... otherPredicates) {
		CqnPredicate conjunction = predicate;
		for (Optional pred : otherPredicates) {
			if (pred.isPresent()) {
				conjunction = new Conjunction(pred.get(), conjunction);
			}
		}
		return conjunction;
	}

	private static CqnDelete createDeleteStatement(CdsModel cdsModel, String entityName, Map values,
			Map fkValues) {
		// Exclude associations, as they are already contained in fkValues
		Map keys = cdsModel.getEntity(entityName).keyElements()
				.filter(k -> !k.getType().isAssociation())
				.collect(toMap(CdsElement::getName, e -> getValue(e, values)));
		keys.putAll(fkValues);
		return Delete.from(entityName).matching(keys);
	}

	private static Object getValue(CdsElement key, Map values) {
		Object val = values.get(key.getName());
		if (val == null) {
			throw new CdsException("Provided data is missing value for key '" + key + "'");
		}
		return val;
	}

	private Map getTargetFkValues(Entry parentEntry) {
		String association = ref.lastSegment();
		CqnPredicate filter = parentEntry.expr.orElseThrow(() -> new UnsupportedOperationException(
				"Statements with path expression are only supported with filter by keys. Structured type reference: "
						+ ref));

		CdsEntity entity = parentEntry.entity;
		Map parentFilterValues = new OnConditionAnalyzer(association, filter, true).getParentPkValues();
		return calculateFkValues(entity, association, parentFilterValues);
	}

	private CqnPredicate getOnConditionPredicate(Entry parentEntry) {
		String association = ref.lastSegment();
		CqnPredicate filter = parentEntry.expr.orElseThrow(() -> new UnsupportedOperationException(
				"Statements with path expression are only supported with filter by keys. Structured type reference: "
						+ ref));
		return new OnConditionAnalyzer(parentEntry.entity.getAssociation(association), true, session)
				.getTargetPredicate(filter);
	}

	private static Entry parentEntry(LinkedList path) {
		Iterator iterator = path.descendingIterator();
		iterator.next();
		return iterator.next();
	}

	private static CdsEntity targetEntity(LinkedList path) {
		Iterator iterator = path.descendingIterator();
		return iterator.next().entity;
	}

	private static List> addValues(List> entries, Map values) {
		List> copy = entries.stream().map(HashMap::new).collect(Collectors.toList());
		copy.forEach(e -> e.putAll(values));
		return copy;
	}

	private LinkedList unfoldPathExpression() {
		LinkedList path = new LinkedList<>();
		path.add(new Entry(cdsModel.getEntity(ref.firstSegment()), ref.segments().get(0).filter()));

		if (ref.segments().size() > 1) {
			ref.segments().stream().skip(1).forEach(s -> {
				CdsEntity e = path.getLast().entity;
				path.add(new Entry(e.getTargetOf(s.id()), s.filter()));
			});
		}

		return path;
	}

	private Map calculateFkValues(CdsEntity entity, String associationName,
			Map filterValues) {
		CdsElement association = entity.getAssociation(associationName);
		if (!isReverseAssociation(association)) {
			throw new UnsupportedOperationException(
					"Path expressions in insert on forward mapped associations are not supported");
		}
		return new OnConditionAnalyzer(association, true, session).getFkValues(filterValues);
	}

	private static boolean isPathExpression(List path) {
		return path.size() > 1;
	}

	private static class Entry {
		CdsEntity entity;
		Optional expr;

		public Entry(CdsEntity entity, Optional expr) {
			this.entity = entity;
			this.expr = expr;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy