com.sap.cds.jdbc.hana.hierarchies.HierarchyFunctionMapper Maven / Gradle / Ivy
The newest version!
/*******************************************************************
* © 2024 SAP SE or an SAP affiliate company. All rights reserved. *
*******************************************************************/
package com.sap.cds.jdbc.hana.hierarchies;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import com.sap.cds.impl.Context;
import com.sap.cds.impl.PreparedCqnStmt.Parameter;
import com.sap.cds.impl.sql.SQLStatementBuilder.SQLStatement;
import com.sap.cds.impl.sql.SelectStatementBuilder;
import com.sap.cds.impl.sql.TokenToSQLTransformer;
import com.sap.cds.jdbc.spi.SqlMapping;
import com.sap.cds.jdbc.spi.TableFunctionMapper;
import com.sap.cds.jdbc.spi.TableNameResolver;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnSortSpecification;
import com.sap.cds.ql.cqn.CqnSource;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.hana.CqnHierarchy;
import com.sap.cds.ql.hana.CqnHierarchyGenerator;
import com.sap.cds.ql.hana.CqnHierarchySubset;
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;
public abstract class HierarchyFunctionMapper implements TableFunctionMapper {
private static final String SOURCE = "SOURCE";
private static final String LPAREN = "(";
private static final String RPAREN = ")";
protected final Context context;
protected final T hierarchy;
protected final List params;
protected final TokenToSQLTransformer toSQL;
protected final CdsModel model;
private HierarchyFunctionMapper(Context context, List params, T hierarchy) {
this.context = context;
this.model = context.getCdsModel();
this.hierarchy = hierarchy;
this.params = params;
this.toSQL = toSQL(context, params, hierarchy.source());
}
private static TokenToSQLTransformer toSQL(Context context, List params, CqnSource source) {
if (source.isRef()) {
CqnStructuredTypeRef ref = source.asRef();
CdsEntity entity = CdsModelUtils.entity(context.getCdsModel(), ref);
SqlMapping sqlMapping = context.getDbContext().getSqlMapping(entity);
String tableName = sqlMapping.tableName();
return TokenToSQLTransformer.notCollating(context, sqlMapping::columnName, entity, tableName, params);
} else if (source.isSelect()) {
CdsStructuredType rowType = CqnStatementUtils.rowType(context.getCdsModel(), source.asSelect());
SqlMapping sqlMapping = context.getDbContext().getSqlMapping(rowType);
return TokenToSQLTransformer.notCollating(context, sqlMapping::columnName, source.asSelect(), rowType,
params);
} else if (source.isTableFunction()) {
return toSQL(context, params, source.asTableFunction().source());
} else {
throw new IllegalStateException();
}
}
@Override
public String toSQL() {
StringBuilder sql = new StringBuilder(hierarchy.name());
sql.append(LPAREN);
sql.append(SOURCE);
sql.append(" ");
CqnSource source = hierarchy.source();
if (source.isRef()) {
CqnStructuredTypeRef ref = source.asRef();
TableNameResolver resolver = context.getTableNameResolver();
CdsEntity entity = CdsModelUtils.entity(context.getCdsModel(), ref);
sql.append(resolver.tableName(entity));
} else if (source.isSelect()) {
sql.append(LPAREN);
CqnSelect subquery = source.asSelect();
SQLStatement stmt = new SelectStatementBuilder(context, params, subquery, new ArrayDeque<>(), true, true).build();
sql.append(stmt.sql());
sql.append(RPAREN);
} else if (source instanceof CqnHierarchy inner) {
String innerSQL = create(context, params, inner).toSQL();
sql.append(innerSQL);
} else {
throw new IllegalStateException();
}
appendClauses(sql);
sql.append(RPAREN);
return new SQLStatement(sql.toString(), List.of()).sql();
}
protected CdsStructuredType rowType() {
return CqnStatementUtils.rowType(model, hierarchy);
}
abstract void appendClauses(StringBuilder builder);
@SuppressWarnings("unchecked")
public static HierarchyFunctionMapper create(Context context, List params,
T hierarchy) {
if (hierarchy.isGenerator()) {
return (HierarchyFunctionMapper) new Generator(context, params, hierarchy.asGenerator());
} else if (hierarchy.isHierarchySubset()) {
return (HierarchyFunctionMapper) new HierarchySubset(context, params, hierarchy.asHierarchySubset());
} else {
throw new IllegalStateException();
}
}
private static class Generator extends HierarchyFunctionMapper {
public Generator(Context context, List params, CqnHierarchyGenerator hierarchy) {
super(context, params, hierarchy);
}
private static final String SIBL_ORDER_BY = "SIBLING ORDER BY";
private static final String DEPTH = "DEPTH";
@Override
void appendClauses(StringBuilder sql) {
List siblingOrderBy = hierarchy.orderBy();
if (!siblingOrderBy.isEmpty()) {
appendSiblingOrderBy(sql, siblingOrderBy);
}
if (hierarchy.depth() != null) {
sql.append(" ").append(DEPTH).append(" ").append(hierarchy.depth());
}
hierarchy.startWhere().map(w -> toSQL.toSQL(rowType(), w)).ifPresent(s ->
sql.append(" START WHERE ").append(s));
sql.append(" NO CACHE");
}
private void appendSiblingOrderBy(StringBuilder sql, List siblingOrderBy) {
sql.append(" ").append(SIBL_ORDER_BY).append(" ");
sql.append(siblingOrderBy.stream().map(o -> sort(o)).collect(Collectors.joining(", ")));
}
private String sort(CqnSortSpecification o) {
StringBuilder sort = new StringBuilder(toSQL.apply(o.value()));
sort.append(" " + SelectStatementBuilder.sortOrderToSql(o));
return sort.toString();
}
}
private static class HierarchySubset extends HierarchyFunctionMapper {
public HierarchySubset(Context context, List params, CqnHierarchySubset hierarchyNavigation) {
super(context, params, hierarchyNavigation);
}
@Override
void appendClauses(StringBuilder sql) {
Optional startWhere = hierarchy.startWhere();
startWhere.map(w -> toSQL.toSQL(rowType(), w)).ifPresent(s ->
sql.append(" START WHERE ").append(s));
// distance
sql.append(" DISTANCE").append(optional(" FROM ", hierarchy.from())).append(optional(" TO ", hierarchy.to()));
}
String optional(String s, int i) {
if (Integer.MAX_VALUE == i || Integer.MIN_VALUE == i) {
return "";
}
return s + i;
}
}
}