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

com.sap.cds.impl.AssociationAnalyzer 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.impl;

import static com.sap.cds.impl.builder.model.ComparisonPredicate.eq;
import static com.sap.cds.impl.builder.model.Connectors.and;
import static com.sap.cds.impl.builder.model.ElementRefImpl.element;
import static com.sap.cds.ql.impl.ExpressionVisitor.copy;
import static java.util.Comparator.comparing;

import java.util.Optional;

import com.sap.cds.impl.builder.model.Connectors;
import com.sap.cds.ql.ElementRef;
import com.sap.cds.ql.Predicate;
import com.sap.cds.ql.Value;
import com.sap.cds.ql.cqn.CqnComparisonPredicate.Operator;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnModifier;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnReference;
import com.sap.cds.ql.cqn.CqnSyntaxException;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.ql.impl.ExpressionVisitor;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;

public class AssociationAnalyzer {

	public CqnPredicate getOnCondition(CdsElement association) {
		CdsAssociationType assoc = association.getType();
		Optional on = assoc.onCondition();
		if (on.isPresent()) {
			return resolveOnCondition(on.get(), association);
		}
		return managedOn(association);
	}

	private CqnPredicate resolveOnCondition(CqnPredicate on, CdsElement association) {
		on = copy(on, new SelfResolver(association));

		return on;
	}

	private static CqnPredicate managedOn(CdsElement association) {
		return association.getType().as(CdsAssociationType.class).keys().sorted(comparing(CdsElement::getName))
				.map(key -> eqForward(association.getName(), key, key.getName())).collect(and());
	}

	private static Predicate eqForward(String assoc, CdsElement key, String keyName) {
		if (key.getType().isAssociation()) {
			return key.getType().as(CdsAssociationType.class).keys()
					.map(k -> eqForward(assoc, k, fkName(keyName, k.getName()))).collect(Connectors.and());
		}
		CqnValue lhs = element(fkName(assoc, keyName));
		CqnValue rhs = element(assoc, keyName);

		return eq(lhs, rhs);
	}

	private static String fkName(String association, String element) {
		return association + "_" + element;
	}

	private final class SelfResolver implements CqnModifier {
		private final CdsElement association;

		private SelfResolver(CdsElement association) {
			this.association = association;
		}

		@Override
		public Predicate comparison(Value lhs, Operator op, Value rhs) {
			if (isSelfRef(lhs)) {
				assertEqRef(op, rhs);
				return self(association, rhs.asRef());
			} else if (isSelfRef(rhs)) {
				assertEqRef(op, lhs);
				return self(association, lhs.asRef());
			}
			return CqnModifier.super.comparison(lhs, op, rhs);
		}

		private void assertEqRef(Operator op, CqnValue val) {
			if (op != Operator.EQ || !val.isRef()) {
				throw new CqnSyntaxException("$self must be compared with '=' ref");
			}
		}

		private boolean isSelfRef(CqnValue value) {
			if (!value.isRef()) {
				return false;
			}
			return isSelfRef(value.asRef());
		}

		private boolean isSelfRef(CqnElementRef ref) {
			if (ref.segments().size() == 1) {
				return "$self".equals(ref.firstSegment());
			}
			return false;
		}

		private Predicate self(CdsElement association, CqnReference backlink) {
			// TODO add support for multiple path segments
			// to allow for structured types containing associations
			String assocName = association.getName();
			if (!backlink.firstSegment().equals(assocName) || backlink.segments().size() != 2) {
				throw new UnsupportedOperationException("Unsupported on condition in association "
						+ association.getDeclaringType() + "." + association);
			}
			CdsEntity entity = association.getDeclaringType();
			CdsEntity assocTarget = entity.getTargetOf(assocName);
			String backlinkName = backlink.segments().get(1).id();
			CdsElement backLinkAssoc = assocTarget.getAssociation(backlinkName);
			Optional backlinkOn = backLinkAssoc.getType().as(CdsAssociationType.class).onCondition();
			if (backlinkOn.isPresent()) {
				return backlinkOn(assocName, backlinkName, backlinkOn.get());
			}
			return backLinkAssoc.getType().as(CdsAssociationType.class).keys().sorted(comparing(CdsElement::getName))
					.map(key -> eqReverse(assocName, backlinkName, key, key.getName())).collect(and());
		}

		private Predicate backlinkOn(String assocName, String backlinkName, CqnPredicate on) {
			return ExpressionVisitor.copy(on, new CqnModifier() {
				@Override
				public Value ref(ElementRef ref) {
					if (ref.firstSegment().equals(backlinkName)) {
						return element(ref.lastSegment());
					} else {
						return element(assocName, ref.firstSegment());
					}
				}
			});
		}

		private Predicate eqReverse(String assoc, String backlinkName, CdsElement key, String keyName) {
			if (key.getType().isAssociation()) {
				return key.getType().as(CdsAssociationType.class).keys()
						.map(k -> eqReverse(assoc, backlinkName, k, fkName(keyName, k.getName()))).collect(and());
			}
			CqnValue lhs = element(assoc, fkName(backlinkName, keyName));
			CqnValue rhs = element(keyName);

			return eq(lhs, rhs);
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy