com.sap.cds.impl.qat.QatBuilder Maven / Gradle / Ivy
The 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.CqnTableFunction;
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()) {
CqnSelect subquery = source.asSelect();
CdsStructuredType rowType = CqnStatementUtils.rowType(context.getCdsModel(), subquery);
this.root = new QatSelectRootNode(subquery, rowType);
} else if (source.isTableFunction()) {
CqnTableFunction tableFunction = source.asTableFunction();
CdsStructuredType rowType = CqnStatementUtils.rowType(context.getCdsModel(), tableFunction);
this.root = new QatTableFunctionRootNode(tableFunction, 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 extends Segment> 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 extends Segment> segments, boolean inSource) {
for (Segment seg : segments) {
current = addChild(current, seg, inSource);
traverseFilter(current, seg);
if (current instanceof QatElementNode en && en.isJson()) {
break;
}
}
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 extends Segment> skipFirst(CqnReference ref) {
List extends Segment> segments = ref.segments();
return segments.subList(1, segments.size());
}
}