All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.sap.cds.ql.impl.CqnNormalizer Maven / Gradle / Ivy
/************************************************************************
* © 2020-2023 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cds.ql.impl;
import static com.sap.cds.DataStoreConfiguration.SELECT_STAR;
import static com.sap.cds.DataStoreConfiguration.SELECT_STAR_COLUMNS;
import static com.sap.cds.DataStoreConfiguration.UNIVERSAL_CSN;
import static com.sap.cds.util.CqnStatementUtils.isToOnePath;
import static com.sap.cds.util.CqnStatementUtils.removeVirtualElements;
import static com.sap.cds.util.CqnStatementUtils.resolveExpands;
import static com.sap.cds.util.CqnStatementUtils.resolveKeyPlaceholder;
import static com.sap.cds.util.CqnStatementUtils.resolveStar;
import static com.sap.cds.util.CqnStatementUtils.resolveStructureComparison;
import static com.sap.cds.util.CqnStatementUtils.resolveVirtualElements;
import static com.sap.cds.util.CqnStatementUtils.simplify;
import static com.sap.cds.util.CqnStatementUtils.unfoldInline;
import static com.sap.cds.util.NestedStructsResolver.resolveNestedStructs;
import static com.sap.cds.util.PathExpressionResolver.resolvePath;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.sap.cds.DataStoreConfiguration;
import com.sap.cds.SessionContext;
import com.sap.cds.impl.Context;
import com.sap.cds.impl.DraftUtils;
import com.sap.cds.jdbc.spi.SearchResolver;
import com.sap.cds.ql.CQL;
import com.sap.cds.ql.ElementRef;
import com.sap.cds.ql.Predicate;
import com.sap.cds.ql.Select;
import com.sap.cds.ql.cqn.CqnAnalyzer;
import com.sap.cds.ql.cqn.CqnDelete;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnExpand;
import com.sap.cds.ql.cqn.CqnInsert;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnSelectListItem;
import com.sap.cds.ql.cqn.CqnSelectListValue;
import com.sap.cds.ql.cqn.CqnSource;
import com.sap.cds.ql.cqn.CqnStatement;
import com.sap.cds.ql.cqn.CqnUpdate;
import com.sap.cds.ql.cqn.CqnUpsert;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.util.CdsModelUtils;
import com.sap.cds.util.CqnStatementUtils;
import com.sap.cds.util.OnConditionAnalyzer;
public class CqnNormalizer {
private final CdsModel cdsModel;
private final DataStoreConfiguration config;
private final CqnAnalyzer analyzer;
private final Context context;
private SessionContext sessionContext;
private SearchResolver searchResolver;
public CqnNormalizer(Context context) {
this.context = context;
this.cdsModel = context.getCdsModel();
this.sessionContext = context.getSessionContext();
this.analyzer = CqnAnalyzer.create(cdsModel);
this.config = context.getDataStoreConfiguration();
}
private SearchResolver getSearchResolver() {
// Lazy load SearchResolver, as HanaSearchResolver is stateful
// TODO: make Hana search resolver is stateless, by removing 'inSubquery'
if (null == searchResolver) {
this.searchResolver = context.getSearchResolver();
}
return searchResolver;
}
public CqnSelect normalize(CqnSelect select) {
Select> copy = normalizeSubqueries(select);
CdsStructuredType target = CqnStatementUtils.targetType(cdsModel, copy);
boolean includeAssocs = includeManagedAssocs(copy);
resolveStar(copy, target, includeAssocs);
resolveNestedStructs(copy, target, includeAssocs);
if (config.getProperty(DataStoreConfiguration.IGNORE_VIRTUAL_ELEMENTS, true)) {
removeVirtualElements(copy, target);
} else {
resolveVirtualElements(copy, target);
}
resolveKeyPlaceholder(target, copy);
// TODO don't copy
resolveStructureComparison(target, copy);
copy = resolveExpands(copy, target, includeAssocs);
unfoldInline(copy, target);
copy = (Select>) getSearchResolver().resolve(copy);
copy = resolveMatchPredicates(target, copy);
copy = (Select>) DraftUtils.resolveConstantElements(cdsModel, target, copy);
simplify(target, copy);
return copy;
}
private boolean includeManagedAssocs(CqnSelect select) {
return select.from().isRef() && config.getProperty(UNIVERSAL_CSN, false)
&& SELECT_STAR_COLUMNS.equals(config.getProperty(SELECT_STAR));
}
public CqnSelect resolveForwardMappedAssocs(CqnSelect select) {
CdsStructuredType target = CqnStatementUtils.targetType(cdsModel, select);
return resolveForwardMappedAssocs(select, target);
}
private CqnSelect resolveForwardMappedAssocs(CqnSelect select, CdsStructuredType target) {
return SelectBuilder.copy(select, new LeanModifier() {
@Override
public List items(List items) {
List resolved = resolveForwardMappedAssocs(target, items);
return LeanModifier.super.items(resolved);
}
@Override
public CqnSelectListItem expand(CqnExpand expand) {
if (!expand.ref().lastSegment().equals("*")) {
CdsEntity expTarget = CdsModelUtils.entity((CdsEntity) target, expand.ref().segments());
List resolved = resolveForwardMappedAssocs(expTarget, expand.items());
return CQL.copy(expand).items(resolved);
}
return expand;
}
}, false);
}
/*
* Returns a list of slis based on items w/o associations. Forward-mapped
* associations are resolved to FK elements with a structuring alias.
* Reverse-mapped associations cause a UnsupportedOperationException.
*/
private List resolveForwardMappedAssocs(CdsStructuredType target,
List items) {
List slis = new ArrayList<>();
for (CqnSelectListItem item : items) {
if (item.isRef()) {
CdsElement element = CdsModelUtils.element(target, item.asRef());
if (element.getType().isAssociation()) {
slis.addAll(resolveForwardMappedAssoc(target, item.asValue(), element));
continue;
}
}
slis.add(item);
}
return slis;
}
/*
* Returns the FK elements of forward-mapped to-one associations. Reverse-mapped
* associations cause a UnsupportedOperationException.
*/
private List resolveForwardMappedAssoc(CdsStructuredType target, CqnSelectListValue refValue,
CdsElement element) {
List slis = new ArrayList<>();
CqnElementRef ref = refValue.value().asRef();
if (isToOnePath(target, ref.segments()) && !CdsModelUtils.isReverseAssociation(element)) {
OnConditionAnalyzer onCondAnalyzer = new OnConditionAnalyzer(element, false);
onCondAnalyzer.getFkMapping().forEach((fk, r) -> {
if (!fk.contains(".") && r.isRef() && r.asRef().firstSegment().equals(ref.lastSegment())) {
ElementRef> elementRef = CQL.to(ref.segments().subList(0, ref.segments().size() - 1)).get(fk);
slis.add(elementRef.as(refValue.displayName() + "." + r.asRef().displayName()));
}
});
} else {
throw new UnsupportedOperationException("Association " + element.getQualifiedName()
+ " is not supported on the select list. Only forward-mapped to-one associations are supported on the select list");
}
return slis;
}
private S resolveMatchPredicates(CdsStructuredType target, S stmt) {
return new MatchPredicateNormalizer(cdsModel, target).normalize(stmt);
}
private Select> normalizeSubqueries(CqnSelect select) {
CqnSource source = select.from();
if (!source.isSelect()) {
return SelectBuilder.copy(select);
}
// a SearchResolver may push down a search expression
select.search().ifPresent(s -> getSearchResolver().pushDownSearchToSubquery(select, select.from().asSelect()));
final CqnSelect inner = normalize(source.asSelect());
SelectBuilder> outerSelect = (SelectBuilder>) Select.from(inner);
moveAllQueryPartsFromOldSelectToNewOuterSelect(select, outerSelect);
return outerSelect;
}
private void moveAllQueryPartsFromOldSelectToNewOuterSelect(CqnSelect select, SelectBuilder> outer) {
if (select.isDistinct()) {
outer.distinct();
}
if (select.hasInlineCount()) {
outer.inlineCount();
}
outer.columns(select.items());
select.where().ifPresent(outer::where);
select.search().ifPresent(s -> {
Predicate searchExpression = (Predicate) s;
Collection searchableElements = outer.searchableElements();
outer.search(e -> searchExpression, searchableElements);
});
select.having().ifPresent(outer::having);
outer.groupBy(select.groupBy());
outer.orderBy(select.orderBy());
select.limit().ifPresent(l -> outer.limit(l.top(), l.skip()));
outer.excluding(select.excluding());
select.getLock().ifPresent(l -> outer.lock(l.timeout().get()));
}
public CqnInsert normalize(CqnInsert insert) {
insert = resolvePath(cdsModel, insert, sessionContext);
return insert;
}
public CqnUpsert normalize(CqnUpsert upsert) {
upsert = resolvePath(cdsModel, upsert, sessionContext);
return upsert;
}
public CqnUpdate normalize(CqnUpdate update) {
update = resolvePath(cdsModel, update);
return norm(update);
}
public CqnDelete normalize(CqnDelete delete) {
delete = resolvePath(cdsModel, delete);
return norm(delete);
}
private S norm(S stmt) {
CdsEntity target = analyzer.analyze(stmt.ref()).targetEntity();
stmt = resolveKeyPlaceholder(target, stmt);
stmt = resolveStructureComparison(target, stmt);
stmt = resolveMatchPredicates(target, stmt);
return stmt;
}
}