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.6.1
Show newest version
/************************************************************************
 * © 2019-2022 SAP SE or an SAP affiliate company. All rights reserved. *
 ************************************************************************/
package com.sap.cds.util;

import static com.sap.cds.util.CdsModelUtils.isReverseAssociation;
import static com.sap.cds.util.CqnStatementUtils.linkKeysToOuterQuery;

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.function.Consumer;
import java.util.stream.Collectors;

import com.sap.cds.SessionContext;
import com.sap.cds.impl.builder.model.Conjunction;
import com.sap.cds.impl.builder.model.ExistsSubquery;
import com.sap.cds.ql.Delete;
import com.sap.cds.ql.Insert;
import com.sap.cds.ql.Select;
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.CqnSelect;
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;
import com.sap.cds.reflect.CdsStructuredType;

public class PathExpressionResolver {

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

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

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

		if (!isPathExpression(path)) {
			if (path.getFirst().filter != null) {
				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, CqnUpdate update) {
		CqnStructuredTypeRef ref = update.ref();
		CdsEntity target = CdsModelUtils.entity(cdsModel, ref);
		Update resolved = Update.entity(target).entries(update.entries());

		addWhere(ref, target, update.where(), resolved::where);

		return resolved;
	}

	public static CqnSelect resolvePath(CdsModel cdsModel, CqnSelect select) {
		if (select.from().isRef()) {
			CqnStructuredTypeRef ref = select.ref();
			CdsEntity target = CdsModelUtils.entity(cdsModel, ref);
			Select resolved = Select.from(target).columns(select.items()).orderBy(select.orderBy());

			addWhere(ref, target, select.where(), resolved::where);

			return resolved;
		}
		return select;
	}

	public static CqnDelete resolvePath(CdsModel cdsModel, CqnDelete delete) {
		CqnStructuredTypeRef ref = delete.ref();
		CdsEntity target = CdsModelUtils.entity(cdsModel, ref);
		Delete resolved = Delete.from(target);

		addWhere(ref, target, delete.where(), resolved::where);

		return resolved;
	}

	private static void addWhere(CqnStructuredTypeRef ref, CdsEntity target, Optional where,
			Consumer filterable) {
		Optional refFilter;
		if (ref.size() == 1) {
			refFilter = ref.rootSegment().filter();
		} else {
			refFilter = Optional.of(resolveUsingSubquery(target, ref));
		}
		Conjunction.and(refFilter, where).ifPresent(filterable::accept);
	}

	private static CqnPredicate resolveUsingSubquery(CdsStructuredType target, CqnStructuredTypeRef ref) {
		return new ExistsSubquery(Select.from(ref).where(linkKeysToOuterQuery(target)));
	}

	private Map getTargetFkValues(Entry parentEntry) {
		if (null == parentEntry.filter) {
			throw new UnsupportedOperationException(
					"Statements with path expression are only supported with filter by keys. Structured type reference: "
							+ ref);
		}
		String association = ref.lastSegment();
		CdsEntity entity = parentEntry.entity;
		Map parentFilterValues = new OnConditionAnalyzer(association, parentEntry.filter, true)
				.getParentPkValues();
		return calculateFkValues(entity, association, parentFilterValues);
	}

	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.size() > 1) {
			ref.stream().skip(1).forEach(s -> {
				CdsEntity entity = path.getLast().entity;
				path.add(new Entry(entity.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, sessionContext).getFkValues(filterValues);
	}

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

	private static class Entry {
		CdsEntity entity;
		CqnPredicate filter;

		public Entry(CdsEntity entity, Optional filter) {
			this.entity = entity;
			this.filter = filter.orElse(null);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy