com.sap.cds.jdbc.hana.hierarchies.HanaHierarchyResolver Maven / Gradle / Ivy
/*******************************************************************
* © 2024 SAP SE or an SAP affiliate company. All rights reserved. *
*******************************************************************/
package com.sap.cds.jdbc.hana.hierarchies;
import static com.sap.cds.ql.cqn.transformation.CqnTransformation.Kind.IDENTITY;
import static com.sap.cds.ql.cqn.transformation.CqnTransformation.Kind.ORDERBY;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import com.sap.cds.impl.builder.model.InSubquery;
import com.sap.cds.ql.CQL;
import com.sap.cds.ql.ElementRef;
import com.sap.cds.ql.Literal;
import com.sap.cds.ql.Predicate;
import com.sap.cds.ql.Select;
import com.sap.cds.ql.cqn.CqnPredicate;
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.CqnSortSpecification;
import com.sap.cds.ql.cqn.transformation.CqnAncestorsTransformation;
import com.sap.cds.ql.cqn.transformation.CqnDescendantsTransformation;
import com.sap.cds.ql.cqn.transformation.CqnFilterTransformation;
import com.sap.cds.ql.cqn.transformation.CqnHierarchySubsetTransformation;
import com.sap.cds.ql.cqn.transformation.CqnSearchTransformation;
import com.sap.cds.ql.cqn.transformation.CqnTopLevelsTransformation;
import com.sap.cds.ql.cqn.transformation.CqnTransformation;
import com.sap.cds.ql.cqn.transformation.CqnTransformation.Kind;
import com.sap.cds.ql.hana.HANA;
import com.sap.cds.ql.hana.Hierarchy;
import com.sap.cds.ql.hana.HierarchySubset;
import com.sap.cds.ql.impl.SelectBuilder;
import com.sap.cds.reflect.CdsBaseType;
import com.sap.cds.util.CaseBuilder;
import com.sap.cds.util.transformations.TransformationToSelect;
public class HanaHierarchyResolver extends TransformationToSelect {
private static final Literal ZERO = CQL.constant(0);
private static final Literal ONE = CQL.constant(1);
// HANA elements
private static final String PARENT_ID = "parent_id";
private static final String NODE_ID = "node_id";
private static final String HIERARCHY_TREE_SIZE = "hierarchy_tree_size";
private static final String HIERARCHY_LEVEL = "hierarchy_level";
private static final String HIERARCHY_RANK = "hierarchy_rank";
// OData elements
private static final String DISTANCE_FROM_ROOT = "DistanceFromRoot";
private static final String LIMITED_DESCENDANT_COUNT = "LimitedDescendantCount";
private static final String DESCENDANT_COUNT = "DescendantCount";
private static final String DRILL_STATE = "DrillState";
private boolean isHierarchicalSelect;
// drill states
private static final Literal COLLAPSED = CQL.constant("collapsed");
private static final Literal EXPANDED = CQL.constant("expanded");
private static final Literal LEAF = CQL.constant("leaf");
private Hierarchy sourceHierarchy;
private List transformations;
private List originalItems;
public HanaHierarchyResolver(Select> select) {
super(select);
}
@Override
protected void before(CqnSelect original) {
originalItems = original.items();
isHierarchicalSelect = original.transformations().stream().anyMatch(HanaHierarchyResolver::isHierarchical);
if (isHierarchicalSelect) {
this.transformations = original.transformations();
Select> hierarchySource = select;
sourceHierarchy = HANA.hierarchy(hierarchySource);
select = SelectBuilder.from(sourceHierarchy)
.columns(CQL.star(),
descendantCount(), //
distanceFromRoot()) //
.excluding(HIERARCHY_TREE_SIZE, HIERARCHY_LEVEL, HIERARCHY_RANK);
}
}
@Override
protected void after() {
if (isHierarchicalSelect) {
var iter = transformations.listIterator(transformations.size());
while (iter.hasPrevious()) {
CqnTransformation t = iter.previous();
if (t.kind() == Kind.DESCENDANTS) {
addDescendantsDrillState((SelectBuilder>) select, t);
break;
}
}
}
}
@Override
protected void copySelectList(List slis) {
if (!isHierarchicalSelect) {
super.copySelectList(slis);
}
// don't modify select list, as we add calculated elements
}
private static boolean isHierarchical(CqnTransformation t) {
return switch (t.kind()) {
case TOPLEVELS, ANCESTORS, DESCENDANTS -> true;
default -> false;
};
}
@Override
protected void applyTopLevels(CqnTopLevelsTransformation topLevels) {
wrapUnless(topLevels, IDENTITY, ORDERBY);
previous = CqnTransformation.IDENTITY; // avoid wrapping in case of ORDER BYs
Predicate filter = CQL.TRUE;
// TODO: optimize to use depth instead where clause
long levels = topLevels.levels();
if (levels > 0) {
// sub-select aliases {hierarchy_level} to {DistanceFromRoot = hierarchy_level - 1}
filter = select.from().isSelect()
? filter.and(CQL.get(DISTANCE_FROM_ROOT).lt(levels))
: filter.and(CQL.get(HIERARCHY_LEVEL).le(levels));
}
CqnPredicate f = filter.or(expandFilter(topLevels.expandLevels().keySet()));
applyFilter(() -> f);
// wrap into hierarchy select(hierarchy(select))
List siblingOrderBy = new ArrayList<>(select.orderBy());
if (siblingOrderBy.isEmpty()) {
siblingOrderBy = List.of(CQL.get(NODE_ID).asc());
}
select.orderBy(List.of());
Hierarchy hierarchy = HANA.hierarchy(select).orderBy(siblingOrderBy);
select = SelectBuilder.from(hierarchy).columns(computeVirtual(originalItems));
}
private static List computeVirtual(List slis) {
if (slis.isEmpty()) {
List selectList = new ArrayList<>(3);
selectList.add(CQL.star());
selectList.add(limitedDescendantCount());
selectList.add(drillState());
return selectList;
}
return slis.stream().map(HanaHierarchyResolver::computeVirtual).toList();
}
private static CqnSelectListItem computeVirtual(CqnSelectListItem sli) {
if (sli.isRef()) {
String path = sli.asRef().path();
if (LIMITED_DESCENDANT_COUNT.equals(path)) {
return limitedDescendantCount();
}
if (DRILL_STATE.equals(path)) {
return drillState();
}
}
return sli;
}
private static CqnSelectListValue distanceFromRoot() {
return CQL.get(HIERARCHY_LEVEL).minus(ONE).as(DISTANCE_FROM_ROOT);
}
private static CqnSelectListValue descendantCount() {
return CQL.get(HIERARCHY_TREE_SIZE).minus(ONE).as(DESCENDANT_COUNT);
}
private static CqnSelectListValue limitedDescendantCount() {
return CQL.get(HIERARCHY_TREE_SIZE).minus(ONE).as(LIMITED_DESCENDANT_COUNT);
}
private static CqnSelectListItem drillState() {
Predicate hasDescendants = CQL.get(DESCENDANT_COUNT).gt(ZERO);
ElementRef