com.sap.cds.impl.Cascader Maven / Gradle / Ivy
The newest version!
/************************************************************************
* © 2021-2022 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cds.impl;
import static com.sap.cds.util.CdsModelUtils.isCascading;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sap.cds.impl.parser.token.RefSegmentBuilder;
import com.sap.cds.ql.CQL;
import com.sap.cds.ql.Predicate;
import com.sap.cds.ql.StructuredType;
import com.sap.cds.ql.cqn.CqnExistsSubquery;
import com.sap.cds.ql.cqn.CqnPredicate;
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.util.CdsModelUtils.CascadeType;
public class Cascader {
private static final Logger logger = LoggerFactory.getLogger(Cascader.class);
private final CascadeType cascadeType;
private final CdsEntity entity;
private final Set visited = new HashSet<>();
private final LinkedList> paths = new LinkedList<>();
private CqnStructuredTypeRef ref;
private CqnPredicate filter;
private boolean unsupported = false;
private Cascader(CascadeType cascadeType, CdsEntity entity) {
this.cascadeType = cascadeType;
this.entity = entity;
}
public static Cascader create(CascadeType cascadeType, CdsEntity entity) {
return new Cascader(cascadeType, entity);
}
public Cascader from(String path) {
return from(path != null ? CQL.to(path).asRef() : null);
}
public Cascader from(CqnStructuredTypeRef ref) {
this.ref = ref;
return this;
}
public Cascader where(Optional filter) {
filter.ifPresent(f -> f.accept(new CqnVisitor() {
@Override
public void visit(CqnExistsSubquery query) {
logger.debug("Cascading delete on {} cannot be optimized due to condition: {}",
entity.getQualifiedName(), f);
unsupported = true;
}
}));
this.filter = filter.orElse(null);
return this;
}
public boolean cascade(Consumer> action) {
if (unsupported) {
return false;
}
StructuredType> path = ref != null ? CQL.to(RefSegmentBuilder.copy(ref.segments())) : null;
if (path != null && filter != null) {
path.filter(ref.targetSegment().filter().map(f -> CQL.and(f, filter)).orElse((Predicate) filter));
}
boolean acyclic = cascade(path, entity);
if (acyclic) {
paths.forEach(action::accept);
}
return acyclic;
}
private boolean cascade(StructuredType> path, CdsEntity entity) {
return entity.associations().filter(a -> isCascading(cascadeType, a))
.allMatch(assoc -> cascade(path, assoc));
}
private boolean cascade(StructuredType> path, CdsElement association) {
CdsAssociationType assocType = association.getType();
CdsEntity target = assocType.getTarget();
if (!visited.add(association.getQualifiedName())) {
logger.debug("Cascading delete on {} cannot be optimized due to cycle in delete graph: {}",
entity.getQualifiedName(), association.getQualifiedName());
return false;
}
StructuredType> p = path != null ? path.to(association.getName()) : CQL.to(association.getName());
paths.addFirst(p);
return cascade(p, target);
}
}