com.sap.cds.ql.impl.ExpandProcessor Maven / Gradle / Ivy
The newest version!
/************************************************************************
* © 2023-2024 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cds.ql.impl;
import static com.sap.cds.util.CqnStatementUtils.HIDDEN_PREFIX;
import static com.sap.cds.util.CqnStatementUtils.hasWhereExistsFilter;
import static com.sap.cds.util.CqnStatementUtils.isNoAggregation;
import static com.sap.cds.util.CqnStatementUtils.selected;
import static java.util.stream.Collectors.toList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.sap.cds.CdsDataStore;
import com.sap.cds.Result;
import com.sap.cds.Row;
import com.sap.cds.impl.builder.model.ExpandBuilder;
import com.sap.cds.impl.builder.model.InPredicate;
import com.sap.cds.ql.CQL;
import com.sap.cds.ql.Select;
import com.sap.cds.ql.StructuredType;
import com.sap.cds.ql.cqn.CqnExpand;
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.CqnSelectListItem;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.reflect.CdsAnnotation;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.impl.reader.model.CdsConstants;
import com.sap.cds.util.CdsModelUtils;
import com.sap.cds.util.CqnStatementUtils;
import com.sap.cds.util.DataUtils;
import com.sap.cds.util.OnConditionAnalyzer;
import com.sap.cds.util.PathExpressionResolver;
public class ExpandProcessor {
private static final Logger logger = LoggerFactory.getLogger(ExpandProcessor.class);
private static final String EXPAND_USING_PARENT_KEYS = "parent-keys";
private static final String EXPAND_USING_LOAD_SINGLE = "load-single";
private static final String EXPAND_USING_JOIN = "join";
private static final String EXPAND_USING_SUBQUERY = "subquery";
private static final String FK_PREFIX = HIDDEN_PREFIX + "fk:";
private final CdsModel model;
private final CqnStructuredTypeRef parentRef;
private final CqnStructuredTypeRef expandRef;
private final ExpandBuilder> expand;
private final long queryTop;
private final long querySkip;
private final Map queryHints;
private final Map mappingAliases;
private Map elementMapping;
private String expandMethod;
private ExpandProcessor(CdsModel model, CqnStructuredTypeRef parentRef, Map mappingAliases,
CqnExpand expand, String expandMethod, long queryTop, long querySkip, Map queryHints) {
this.model = model;
this.expand = (ExpandBuilder>) expand;
this.expandRef = expand.ref();
this.parentRef = parentRef;
this.mappingAliases = mappingAliases;
this.expandMethod = expandMethod;
this.queryTop = queryTop;
this.querySkip = querySkip;
this.queryHints = queryHints;
}
public static ExpandProcessor create(CqnSelect query, CdsModel model, CqnStructuredTypeRef parentRef, CdsStructuredType parentType,
Map parentKeyAliases, CqnExpand expand, boolean pathExpand) {
boolean toOne = CqnStatementUtils.isToOnePath(parentType, expand.ref().segments());
CdsElement assoc = CdsModelUtils.element(parentType, expand.ref().segments());
String expandMethod = determinExpandMethod(parentRef, parentType, expand, assoc, toOne || !pathExpand);
ExpandProcessor expandProcessor = new ExpandProcessor(model, parentRef, parentKeyAliases, expand, expandMethod,
query.top(), query.skip(), query.hints());
expandProcessor.computeElementMapping(assoc);
return expandProcessor;
}
public Map getMappingAliases() {
return mappingAliases;
}
private static String determinExpandMethod(CqnStructuredTypeRef parentRef, CdsStructuredType type, CqnExpand expand, CdsElement assoc,
boolean parentKeys) {
if (parentKeys || expand.hasLimit() || ((ExpandBuilder>) expand).lazy() || hasWhereExistsFilter(parentRef)) {
return EXPAND_USING_PARENT_KEYS;
}
// to-many
return (String) assoc.findAnnotation(CdsConstants.ANNOTATION_JAVA_EXPAND + ".using")
.map(CdsAnnotation::getValue).orElseGet(() -> pathExpandMethod(type, expand));
}
private static String pathExpandMethod(CdsStructuredType type, CqnExpand expand) {
if (CqnStatementUtils.isOneToManyPath(type, expand.ref().segments())) {
return EXPAND_USING_JOIN;
}
return EXPAND_USING_SUBQUERY; // many-to-many
}
private void computeElementMapping(CdsElement assoc) {
try {
elementMapping = fkMapping(expandRef, assoc);
} catch (Exception e) {
this.expandMethod = EXPAND_USING_PARENT_KEYS;
if (isPathExpand()) {
logger.debug("Cannot optimize to-many " + assoc.getQualifiedName() + " expand due to on condition", e);
}
elementMapping = Collections.emptyMap();
}
}
private static Map fkMapping(CqnStructuredTypeRef ref, CdsElement toManyAssoc) {
HashMap mapping = new HashMap<>();
new OnConditionAnalyzer(toManyAssoc, true).getFkMapping().forEach((k, val) -> {
List segments = ref.stream().map(CqnReference.Segment::id).collect(toList());
if (val.isRef() && !CdsModelUtils.isContextElementRef(val.asRef())) {
segments.set(segments.size() - 1, val.asRef().lastSegment());
mapping.put(k, Joiner.on('.').join(segments));
}
});
return mapping;
}
public boolean isPathExpand() {
return switch (expandMethod) {
case EXPAND_USING_JOIN, EXPAND_USING_SUBQUERY -> true;
default -> false;
};
}
public boolean isLoadSingle() {
return EXPAND_USING_LOAD_SINGLE.equals(expandMethod);
}
public ExpandBuilder> getExpand() {
return expand;
}
public boolean hasCountAndLimit() {
return expand.hasInlineCount() && expand.hasLimit();
}
public void addMappingKeys(CqnSelect select) {
if (!elementMapping.isEmpty()) {
List missing = elementMapping.values().stream().filter(e -> !mappingAliases.containsKey(e)).toList();
if (!missing.isEmpty()) {
boolean addMissing = !select.isDistinct() && isNoAggregation(select);
mappingAliases.putAll(selected(missing, select, addMissing));
}
}
}
public void expand(List