com.sap.cds.impl.sql.TokenToSQLTransformer Maven / Gradle / Ivy
/**************************************************************************
* (C) 2020-2024 SAP SE or an SAP affiliate company. All rights reserved. *
**************************************************************************/
package com.sap.cds.impl.sql;
import static com.sap.cds.impl.parser.token.CqnBoolLiteral.FALSE;
import static com.sap.cds.impl.parser.token.CqnBoolLiteral.TRUE;
import static java.util.stream.Collectors.joining;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import com.sap.cds.impl.Context;
import com.sap.cds.impl.PreparedCqnStmt.CqnParam;
import com.sap.cds.impl.PreparedCqnStmt.Parameter;
import com.sap.cds.impl.PreparedCqnStmt.ValueParam;
import com.sap.cds.impl.builder.model.Disjunction;
import com.sap.cds.impl.builder.model.ExistsSubquery;
import com.sap.cds.impl.qat.QatElementNode;
import com.sap.cds.impl.qat.QatEntityNode;
import com.sap.cds.impl.qat.QatEntityRootNode;
import com.sap.cds.impl.qat.QatSelectableNode;
import com.sap.cds.impl.sql.SQLStatementBuilder.SQLStatement;
import com.sap.cds.impl.util.Stack;
import com.sap.cds.jdbc.spi.DbContext;
import com.sap.cds.jdbc.spi.ScalarValueResolver;
import com.sap.cds.ql.cqn.CqnArithmeticExpression;
import com.sap.cds.ql.cqn.CqnBetweenPredicate;
import com.sap.cds.ql.cqn.CqnBooleanLiteral;
import com.sap.cds.ql.cqn.CqnComparisonPredicate;
import com.sap.cds.ql.cqn.CqnConnectivePredicate;
import com.sap.cds.ql.cqn.CqnContainmentTest;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnExistsSubquery;
import com.sap.cds.ql.cqn.CqnExpression;
import com.sap.cds.ql.cqn.CqnFunc;
import com.sap.cds.ql.cqn.CqnInPredicate;
import com.sap.cds.ql.cqn.CqnListValue;
import com.sap.cds.ql.cqn.CqnLiteral;
import com.sap.cds.ql.cqn.CqnNegation;
import com.sap.cds.ql.cqn.CqnNullValue;
import com.sap.cds.ql.cqn.CqnNumericLiteral;
import com.sap.cds.ql.cqn.CqnParameter;
import com.sap.cds.ql.cqn.CqnPlain;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnStringLiteral;
import com.sap.cds.ql.cqn.CqnToken;
import com.sap.cds.ql.cqn.CqnVector;
import com.sap.cds.ql.cqn.CqnVisitor;
import com.sap.cds.ql.impl.CqnNormalizer;
import com.sap.cds.ql.impl.Xpr;
import com.sap.cds.reflect.CdsBaseType;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.util.CdsModelUtils;
import com.sap.cds.util.CqnStatementUtils;
import com.sap.cds.util.SessionUtils;
import com.sap.cds.util.SessionUtils.SessionContextVariable;
public class TokenToSQLTransformer implements Function {
public static final String SQL_TRUE = "TRUE";
public static final String SQL_FALSE = "FALSE";
private static final int SUBSTRING_START_PARAM = 1;
private final Context context;
private final List params;
private final Function aliasResolver;
private final Deque outer;
private final boolean noCollating;
private final Function paramResolver;
private final CqnNormalizer cqnNormalizer;
private final ScalarValueResolver scalarValueResolver;
private int parameterPosition = 0;
public TokenToSQLTransformer(Context context, List params, Function aliasResolver,
Deque outer, Function paramResolver, boolean noCollating) {
this.context = context;
this.params = params;
this.aliasResolver = aliasResolver;
this.outer = outer;
this.noCollating = noCollating;
this.paramResolver = paramResolver;
this.cqnNormalizer = new CqnNormalizer(context);
this.scalarValueResolver = context.getDbContext().getScalarValueResolver();
}
public TokenToSQLTransformer(Context context, List params, Function aliasResolver,
Deque outer, boolean noCollating) {
this(context, params, aliasResolver, outer, parameter -> "?", noCollating);
}
public static TokenToSQLTransformer notCollating(Context context, Function aliasResolver,
CdsEntity entity, String tableName, List params, Function paramResolver) {
return new TokenToSQLTransformer(context, params, aliasResolver, outerQat(entity, tableName), paramResolver,
true);
}
public static TokenToSQLTransformer notCollating(Context context, List params,
Function aliasResolver, Deque outer) {
return new TokenToSQLTransformer(context, params, aliasResolver, outer, true);
}
public static TokenToSQLTransformer notCollating(Context context, Function aliasResolver,
CdsEntity entity, String tableName, List params) {
return new TokenToSQLTransformer(context, params, aliasResolver, outerQat(entity, tableName), true);
}
private static Deque outerQat(CdsEntity entity, String tableName) {
QatEntityNode root = new QatEntityRootNode(entity);
CdsModelUtils.columnsOf(entity).forEach(e -> root.addChild(new QatElementNode(root, e)));
root.setAlias(tableName);
Deque outer = new ArrayDeque<>();
outer.add(root);
return outer;
}
public String toSQL(CdsStructuredType rowType, CqnPredicate pred) {
DbContext dbContext = context.getDbContext();
pred = dbContext.getPredicateMapper().apply(pred);
pred = ContainsToLike.transform(dbContext.getFunctionMapper(), pred);
pred = CqnStatementUtils.simplifyPredicate(rowType, pred);
if (pred == TRUE) {
return null;
}
if (pred == FALSE) {
return "1 = 0";
}
return apply(pred);
}
public String toSQL(CqnPredicate pred) {
return toSQL(null, pred);
}
@Override
public String apply(CqnToken token) {
if (token == null) {
throw new IllegalArgumentException("token must not be null");
}
ToSQLVisitor visitor = new ToSQLVisitor();
token.accept(visitor);
String sql = visitor.get(token);
if (token instanceof Xpr) {
sql = sql.substring(1, sql.length() - 1);
}
return sql;
}
class ToSQLVisitor implements CqnVisitor {
Stack stack = new Stack<>();
@Override
public void visit(CqnParameter p) {
String name = p.isPositional() ? String.valueOf(parameterPosition++) : p.name();
Parameter param = new CqnParam(name).type(p.type());
params.add(param);
push(scalarValueResolver.parameter(p));
}
@Override
public void visit(CqnFunc cqnFunc) {
String func = cqnFunc.func().toLowerCase(Locale.US);
List args = stack.pop(cqnFunc.args().size());
if ("substring".equals(func)) {
// increment the start pos as OData and CQN start at 0 but SQL starts at 1
args.set(SUBSTRING_START_PARAM, args.get(SUBSTRING_START_PARAM) + " + 1");
}
push(context.getDbContext().getFunctionMapper().toSql(func, args));
}
@Override
public void visit(CqnListValue listValue) {
int n = (int) listValue.values().count();
String sql = stack.pop(n).stream().collect(joining(", ", "(", ")"));
push(sql);
}
@Override
public void visit(CqnContainmentTest test) {
throw new IllegalStateException("CQN containment test should be transformed to LIKE predicate");
}
@Override
public void visit(CqnPlain plain) {
push(plain.plain());
}
@Override
public void visit(CqnBooleanLiteral bool) {
push(scalarValueResolver.literal(bool));
}
@Override
public void visit(CqnNumericLiteral> number) {
Number val = number.value();
if (number.isConstant()) {
push(String.valueOf(val));
} else {
valueParam(number);
}
}
@Override
public void visit(CqnStringLiteral literal) {
if (literal.isConstant()) {
push(SQLHelper.literal(literal.value()));
} else {
valueParam(literal);
}
}
@Override
public void visit(CqnVector vector) {
params.add(new ValueParam(vector::value).type(CdsBaseType.VECTOR));
push(scalarValueResolver.parameter(vector));
}
@Override
public void visit(CqnLiteral> literal) {
valueParam(literal);
}
private void valueParam(CqnLiteral> literal) {
Parameter param = new ValueParam(literal::value).type(literal.type());
params.add(param);
push(paramResolver.apply(param));
}
private void valueParam(Supplier
© 2015 - 2024 Weber Informatics LLC | Privacy Policy