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

com.sap.cds.impl.qat.QatBuilder Maven / Gradle / Ivy

There is a newer version: 3.4.0
Show newest version
/************************************************************************
 * © 2019-2023 SAP SE or an SAP affiliate company. All rights reserved. *
 ************************************************************************/
package com.sap.cds.impl.qat;

import java.util.List;
import java.util.Optional;

import com.sap.cds.impl.AssociationAnalyzer;
import com.sap.cds.impl.Context;
import com.sap.cds.impl.CqnValidatorImpl;
import com.sap.cds.impl.builder.model.ExistsSubquery;
import com.sap.cds.impl.util.Stack;
import com.sap.cds.ql.cqn.CqnContainmentTest;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnExistsSubquery;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnReference;
import com.sap.cds.ql.cqn.CqnReference.Segment;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnSource;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnVisitor;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.util.CdsModelUtils;
import com.sap.cds.util.CqnStatementUtils;

public class QatBuilder {

	private static final int T = 'T';
	public static final String ROOT_ALIAS = alias(0, 0);

	private final CqnSelect select;
	private final int queryDepth;
	private final QatSelectableNode root;

	public QatBuilder(Context context, CqnSelect select, int queryDepth) {
		this.select = select;
		this.queryDepth = queryDepth;
		CqnSource source = select.from();
		if (source.isRef()) {
			Segment rootSegment = source.asRef().rootSegment();
			CdsEntity rootEntity = context.getCdsModel().getEntity(rootSegment.id());
			this.root = new QatEntityRootNode(rootEntity, rootSegment.filter());
		} else if (source.isSelect()) {
			CdsStructuredType rowType = CqnStatementUtils.rowType(context.getCdsModel(), source.asSelect());
			this.root = new QatSelectRootNode(source.asSelect(), rowType);
		} else {
			throw new UnsupportedOperationException("Joins are not supported");
		}
	}

	public QatSelectableNode create() {
		collectRefs();
		assignTableAliases();

		return root;
	}

	private void assignTableAliases() {
		QatVisitor assignAlias = new QatVisitor() {
			int i = 0;

			@Override
			public void visit(QatSelectableNode root) {
				assignAlias(root);
			}

			@Override
			public void visit(QatAssociationNode association) {
				assignAlias(association);
			}

			private void assignAlias(QatSelectableNode node) {
				node.setAlias(alias(queryDepth, i++));
			}

		};

		QatTraverser.take(assignAlias).traverse(root);
	}

	private static String alias(int i, int j) {
		return String.valueOf((char) (T + i)) + j;
	}

	private void collectRefs() {
		CqnVisitor visitor = new CollectRefsVisitor(root);
		select.accept(visitor);
	}

	class CollectRefsVisitor implements CqnVisitor {
		private final AssociationAnalyzer associationAnalyzer = new AssociationAnalyzer();
		private final String rootEntity;
		private final Stack base = new Stack<>();

		private CollectRefsVisitor(QatSelectableNode root) {
			this.base.push(root);
			this.rootEntity = root.rowType().getQualifiedName();
		}

		@Override
		public void visit(CqnStructuredTypeRef ref) {
			ref.rootSegment().filter().ifPresent(f -> f.accept(this));

			// append subsequent source path segments, e.g. Select from Publisher.books
			QatNode root = followRef(base.pop(), skipFirst(ref), true);
			base.push(root);
		}

		@Override
		public void visit(CqnElementRef ref) {
			if (!ref.firstSegment().equals(CqnExistsSubquery.OUTER) && !CdsModelUtils.isContextElementRef(ref)) {
				List segments = ref.segments();
				if (segments.size() > 1 && ref.firstSegment().equals(rootEntity)) {
					// qualified usage of element ref, e.g. where Books.title = 'X'
					segments = skipFirst(ref);
				}
				followRef(base.peek(), segments, false);
			}
		}

		@Override
		public void visit(CqnExistsSubquery exists) {
			// set outer QAT
			QatNode outer = base.peek();
			((ExistsSubquery) exists).setOuter(outer);

			CqnVisitor v = new CqnVisitor() {
				@Override
				public void visit(CqnElementRef ref) {
					if (ref.firstSegment().equals(CqnExistsSubquery.OUTER)) {
						// collect outer references into outer QAT
						followRef(outer, skipFirst(ref), false);
					}
				}
			};

			exists.subquery().accept(v);
		}

		@Override
		public void visit(CqnContainmentTest test) {
			test.args().forEach(a -> a.accept(this));
		}

		private QatNode followRef(QatNode current, List segments, boolean inSource) {
			for (Segment seg : segments) {
				current = addChild(current, seg, inSource);
				traverseFilter(current, seg);
			}

			return current;
		}

		private QatNode addChild(QatNode node, Segment seg, boolean inSource) {
			String id = seg.id();
			Optional filter = seg.filter();
			CdsStructuredType rowType = ((QatStructuredNode) node).rowType();
			CdsElement element = rowType.getElement(id);
			if (element.isVirtual()) {
				throw CqnValidatorImpl.virtualElementException(element.getName());
			}
			CdsType type = element.getType();
			QatNode child;
			if (type.isAssociation()) {
				QatAssociation assoc = handleAssociation(element);
				child = new QatAssociationNode(node, assoc, filter, inSource);
			} else if (type.isStructured()) {
				child = new QatStructuredElementNode(node, element);
			} else {
				child = new QatElementNode(node, element);
			}

			return node.addChild(child, filter);
		}

		private void traverseFilter(QatNode node, Segment seg) {
			seg.filter().ifPresent(f -> {
				// infix filter relative to segments QAT, e.g.
				// Select from Publisher[cat = 3].books
				// Select from Publisher { books[year = 2000].author }
				base.push(node);
				f.accept(this);
				base.pop();
			});
		}

		private QatAssociation handleAssociation(CdsElement association) {
			CdsEntity target = association.getType().as(CdsAssociationType.class).getTarget();
			CqnPredicate on = associationAnalyzer.getOnCondition(association);
			return new QatAssociation(target, association.getName(), on);
		}

	}

	private static List skipFirst(CqnReference ref) {
		List segments = ref.segments();
		return segments.subList(1, segments.size());
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy