
com.sap.cds.util.CqnStatementUtils Maven / Gradle / Ivy
/*******************************************************************
* © 2020 SAP SE or an SAP affiliate company. All rights reserved. *
*******************************************************************/
package com.sap.cds.util;
import static com.sap.cds.impl.builder.model.LiteralImpl.literal;
import static com.sap.cds.impl.parser.token.CqnBoolLiteral.FALSE;
import static com.sap.cds.ql.CQL.copy;
import static com.sap.cds.ql.CQL.to;
import static com.sap.cds.ql.impl.SelectListValueBuilder.select;
import static com.sap.cds.reflect.CdsBaseType.cdsType;
import static com.sap.cds.reflect.impl.CdsAnnotatableImpl.CdsAnnotationImpl.annotation;
import static com.sap.cds.reflect.impl.CdsArrayedTypeBuilder.arrayedType;
import static com.sap.cds.reflect.impl.CdsElementBuilder.element;
import static com.sap.cds.reflect.impl.CdsSimpleTypeBuilder.simpleType;
import static com.sap.cds.reflect.impl.CdsSimpleTypeBuilder.undefinedType;
import static com.sap.cds.reflect.impl.reader.model.CdsConstants.ANNOTATION_PERSISTENCE_NAME;
import static com.sap.cds.util.CdsModelUtils.element;
import static com.sap.cds.util.CdsModelUtils.entity;
import static com.sap.cds.util.CdsModelUtils.keyNames;
import static com.sap.cds.util.CdsModelUtils.target;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.annotations.VisibleForTesting;
import com.sap.cds.impl.builder.model.Conjunction;
import com.sap.cds.impl.builder.model.Disjunction;
import com.sap.cds.impl.builder.model.ExpressionImpl;
import com.sap.cds.ql.CQL;
import com.sap.cds.ql.ElementRef;
import com.sap.cds.ql.Expand;
import com.sap.cds.ql.Predicate;
import com.sap.cds.ql.RefSegment;
import com.sap.cds.ql.Select;
import com.sap.cds.ql.StructuredTypeRef;
import com.sap.cds.ql.Update;
import com.sap.cds.ql.Value;
import com.sap.cds.ql.cqn.CqnComparisonPredicate;
import com.sap.cds.ql.cqn.CqnConnectivePredicate.Operator;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnLimit;
import com.sap.cds.ql.cqn.CqnModifier;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnReference.Segment;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnSelectList;
import com.sap.cds.ql.cqn.CqnSelectListItem;
import com.sap.cds.ql.cqn.CqnSelectListValue;
import com.sap.cds.ql.cqn.CqnSortSpecification;
import com.sap.cds.ql.cqn.CqnSource;
import com.sap.cds.ql.cqn.CqnStatement;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnUpdate;
import com.sap.cds.ql.cqn.CqnValidationException;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.ql.cqn.CqnVisitor;
import com.sap.cds.ql.impl.ExpressionVisitor;
import com.sap.cds.ql.impl.SelectBuilder;
import com.sap.cds.ql.impl.UpdateBuilder;
import com.sap.cds.ql.impl.XsertBuilder;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsSimpleType;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.reflect.impl.CdsElementBuilder;
import com.sap.cds.reflect.impl.CdsStructuredTypeBuilder;
public class CqnStatementUtils {
private CqnStatementUtils() {
}
public static boolean containsExpand(CqnSelect select) {
return select.items().stream().anyMatch(i -> i instanceof Expand>);
}
public static Stream selectedRefs(CqnSelect select) {
return select.items().stream().filter(CqnSelectListItem::isValue).map(CqnSelectListItem::asValue)
.filter(i -> i.value().isRef());
}
public static void ensureKeysAreSelected(CdsStructuredType rowType, CqnSelect select) {
List keys = rowType.concreteNonAssociationElements().filter(CdsElement::isKey) //
.map(CdsElement::getName).collect(toList());
CqnVisitor visitor = new CqnVisitor() {
@Override
public void visit(CqnElementRef elementRef) {
if (!keys.isEmpty() && elementRef.segments().size() == 1) {
keys.remove(elementRef.firstSegment());
}
}
};
select.items().forEach(c -> c.accept(visitor));
if (!keys.isEmpty()) {
SelectBuilder> query = (SelectBuilder>) select;
keys.forEach(k -> {
String alias = "@key_" + k;
query.addItem(select(k).as(alias).build());
query.addExclude(alias);
});
}
}
public static boolean containsRef(List items) {
return items.parallelStream().anyMatch(i -> {
if (i.isValue() && i.asValue().value().isRef()) {
return true;
}
if (i.isSelectList()) {
List slItems = i.asSelectList().items();
return containsSelectStar(slItems) || containsRef(slItems);
}
return false;
});
}
public static CqnPredicate extractTargetFilter(CdsStructuredType rowType, CqnUpdate update, boolean useParameters) {
List predicates = new ArrayList<>();
update.where().ifPresent(predicates::add);
Set keys = keyNames(rowType);
update.elements().filter(keys::contains)
.map(key -> CQL.get(key).eq(useParameters ? CQL.param(key) : update.data().get(key)))
.forEach(predicates::add);
update.entries().forEach(data -> keys.forEach(data::remove));
return ExpressionImpl.join(predicates);
}
public static S resolveKeyPlaceholder(CdsStructuredType rowType, S statement) {
return CQL.copy(statement, new CqnModifier() {
@Override
public Value> ref(ElementRef> ref) {
if (isKeyPlaceholder(ref)) {
boolean singleKey = rowType.keyElements().count() == 1;
if (!singleKey) {
throw new CqnValidationException(
"The entity " + rowType + " must have a single key to be filtered with 'byId'");
}
CdsElement key = rowType.keyElements().findFirst().get(); // NOSONAR
String keyName = key.getName();
ref.rootSegment().id(keyName);
}
return ref;
}
private boolean isKeyPlaceholder(CqnElementRef ref) {
return ref.segments().size() == 1 && CqnElementRef.$KEY.equals(ref.firstSegment());
}
});
}
public static void moveKeyValuesToWhere(CdsStructuredType rowType, CqnUpdate update) {
CqnPredicate filter = extractTargetFilter(rowType, update, true);
((Update>) update).where(filter);
}
public static CqnSelect countAllQuery(CqnUpdate update) {
Select> select = countAll(update);
update.where().ifPresent(select::where);
return select;
}
public static CqnSelect inlineCountQuery(CqnSelect select) {
CqnStructuredTypeRef ref = select.ref();
Select> inner = Select.from(ref);
if (select.isDistinct()) {
inner.columns(select.items());
inner.distinct();
} else {
inner.columns(CQL.plain("1").as("one"));
}
select.where().ifPresent(inner::where);
select.search().ifPresent(inner::search);
inner.groupBy(select.groupBy());
select.having().ifPresent(inner::having);
CqnSelect outer = Select.from(inner).columns(Count.ALL);
return outer;
}
private static Select> countAll(CqnStatement statement) {
CqnStructuredTypeRef ref = statement.ref();
Select> select = Select.from(ref);
select.columns(Count.ALL);
return select;
}
public interface NegationResolver extends CqnModifier {
default Predicate negation(Predicate p) {
return p.not();
}
}
private static class Simplifier implements NegationResolver, LogicalOpertionsSimplifier {
// empty
}
public interface Count {
public static final CqnSelectListItem ALL = CQL.func("COUNT", CQL.plain("*")).type(Long.class).as("count");
long getCount();
}
public static Predicate simplifyPredicate(CqnPredicate pred) {
return ExpressionVisitor.copy(pred, new Simplifier() {
});
}
public interface LogicalOpertionsSimplifier extends CqnModifier {
default Predicate connective(Operator op, List predicates) {
if (op == Operator.AND) {
return predicates.stream().collect(Conjunction._and());
} else {
return predicates.stream().collect(Disjunction._or());
}
}
}
public static List resolveStar(List items, Collection excluding,
CdsStructuredType rowType) {
if (containsSelectStar(items)) {
Stream elements = rowType.concreteNonAssociationElements();
List resolved = elements.flatMap(e -> paths(e)).filter(n -> !excluding.contains(n))
.map(n -> select(n).as(n).build()).collect(toList());
if (items != null) {
items.stream().filter(sli -> !sli.isStar()).forEach(resolved::add);
}
return resolved;
}
return items;
}
private static Stream paths(CdsElement element) {
String name = element.getName();
CdsType type = element.getType();
if (type.isStructured()) {
CdsStructuredType struct = type.as(CdsStructuredType.class);
return struct.concreteNonAssociationElements().flatMap(e -> paths(e)).map(n -> name + "." + n);
}
return Stream.of(name);
}
public static CqnSelect resolveStar(CqnSelect select, CdsStructuredType rowType) {
List items = select.items();
List resolved = resolveStar(items, select.excluding(), rowType);
if (items != resolved) {
return SelectBuilder.copy(select).columns(resolved);
}
return select;
}
public static boolean isSelectStar(List columns) {
if (columns == null || columns.isEmpty()) {
return true;
}
return columns.size() == 1 && columns.get(0).isStar();
}
public static CdsStructuredType targetType(CdsModel model, CqnSelect select) {
return rowType(model, select.from());
}
public static CdsStructuredType rowType(CdsModel model, CqnSource source) {
if (source.isSelect()) {
return rowType(model, source.asSelect());
}
CqnStructuredTypeRef ref = source.asRef();
return CdsModelUtils.entity(model, ref);
}
public static CdsStructuredType rowType(CdsModel model, CqnSelect query) {
CdsStructuredType targetType;
CqnSource source = query.from();
if (source.isRef()) {
if (isSelectStar(query.items()) && query.excluding().isEmpty()) {
return entity(model, source.asRef());
}
targetType = entity(model, source.asRef());
} else if (source.isSelect()) {
targetType = rowType(model, source.asSelect());
} else {
throw new UnsupportedOperationException("Joins are not supported");
}
return rowType(targetType, query.items(), query.excluding()).build();
}
private static CdsStructuredTypeBuilder> rowType(CdsStructuredType targetType, List items,
Collection excluding) {
CdsStructuredTypeBuilder> structBuilder = new CdsStructuredTypeBuilder<>(emptyList(), "");
items.stream().filter(i -> !i.isValue() || !excluding.contains(i.asValue().displayName()))
.forEach(i -> addElements(structBuilder, targetType, i));
return structBuilder;
}
private static void addElements(CdsStructuredTypeBuilder> structBuilder, CdsStructuredType type,
CqnSelectListItem sli) {
if (sli.isStar()) {
type.concreteNonAssociationElements().map(CdsElementBuilder::copy)
.forEach(eb -> structBuilder.addElement(eb));
} else if (sli.isValue()) {
CqnSelectListValue slv = sli.asValue();
String displayName = slv.displayName();
addElement(structBuilder, type, slv, displayName);
} else if (sli.isSelectList()) {
addSelectListElements(structBuilder, type, sli.asSelectList());
} else {
throw new UnsupportedOperationException("Unsupported item type " + sli);
}
}
private static void addElement(CdsStructuredTypeBuilder> structBuilder, CdsStructuredType root,
CqnSelectListValue slv, String displayName) {
CqnValue val = slv.value();
CdsElementBuilder> builder;
if (val.isRef()) {
CdsElement element = element(root, val.asRef());
builder = CdsElementBuilder.copy(element);
} else { // non-ref values
CdsSimpleType type = val.type().map(t -> simpleType(cdsType(t))).orElse(undefinedType());
builder = element(displayName).type(type);
}
Optional alias = slv.alias();
alias.ifPresent(a -> builder.annotation(annotation(ANNOTATION_PERSISTENCE_NAME, a)));
addElement(structBuilder, builder, displayName);
}
private static void addElement(CdsStructuredTypeBuilder> outer, CdsElementBuilder> element,
String displayName) {
int i = displayName.indexOf('.');
if (i == -1) {
element.name(displayName);
outer.addElement(element);
} else {
String prefix = displayName.substring(0, i);
CdsElementBuilder> structuredElement = outer.computeIfAbsent(prefix,
CqnStatementUtils::structuredElement);
String suffix = displayName.substring(i + 1);
CdsStructuredTypeBuilder> inner = (CdsStructuredTypeBuilder>) structuredElement.getTypeBuilder();
addElement(inner, element, suffix);
}
}
private static CdsElementBuilder> structuredElement(String name) {
return new CdsElementBuilder<>(emptyList(), name, new CdsStructuredTypeBuilder<>(emptyList(), ""), false, false,
false, false);
}
private static void addSelectListElements(CdsStructuredTypeBuilder> structBuilder, CdsStructuredType root,
CqnSelectList selectList) {
List extends Segment> refSegments = selectList.ref().segments();
if (refSegments.get(0).id().equals("*")) {
// TODO support expand all associations
return;
}
CdsStructuredType target = target(root, refSegments);
if (selectList.isInline()) {
selectList.items().stream().forEach(i -> addElements(structBuilder, target, i));
} else if (selectList.isExpand()) {
CdsStructuredTypeBuilder> typeBuilder = rowType(target, selectList.items(), emptyList());
CdsType type = isToOnePath(root, refSegments) ? typeBuilder.build() : arrayedType(typeBuilder);
CdsElementBuilder> eb = element(selectList.ref().lastSegment()).type(type);
structBuilder.addElement(eb);
} else {
throw new UnsupportedOperationException("Unsupported select list type: " + selectList);
}
}
private static boolean containsSelectStar(List columns) {
if (columns == null || columns.isEmpty()) {
return true;
}
return columns.parallelStream().anyMatch(CqnSelectListItem::isStar);
}
private static List resolveVirtualElements(List items,
CdsStructuredType target) {
boolean changed = false;
List slis = new ArrayList<>(items.size());
for (CqnSelectListItem sli : items) {
if (sli.isValue()) {
CqnSelectListValue slv = sli.asValue();
CqnValue value = slv.value();
if (value.isRef()) {
CdsElement element = element(target, value.asRef());
if (element.isVirtual() && element.getType().isSimple()) {
changed = true;
CdsSimpleType type = element.getType();
Optional
© 2015 - 2025 Weber Informatics LLC | Privacy Policy