com.sap.cds.impl.docstore.DocStoreUpdateStatementBuilder Maven / Gradle / Ivy
/************************************************************************
* © 2021-2024 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cds.impl.docstore;
import static com.sap.cds.impl.sql.SpaceSeparatedCollector.joining;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.annotations.VisibleForTesting;
import com.sap.cds.impl.Context;
import com.sap.cds.impl.PreparedCqnStmt;
import com.sap.cds.impl.sql.SQLStatementBuilder;
import com.sap.cds.impl.sql.TokenToSQLTransformer;
import com.sap.cds.jdbc.spi.SqlMapping;
import com.sap.cds.ql.cqn.CqnUpdate;
import com.sap.cds.reflect.CdsBaseType;
import com.sap.cds.reflect.CdsEntity;
public class DocStoreUpdateStatementBuilder implements DocStoreStatementBuilder {
private static final String DOUBLEQUOTE = "\"";
private final CqnUpdate update;
private final TokenToSQLTransformer toSql;
private final SqlMapping sqlMapping;
private final String tableName;
private final CdsEntity entity;
private final List params = new ArrayList<>();
public DocStoreUpdateStatementBuilder(Context context, CqnUpdate update) {
this.entity = context.getCdsModel().getEntity(update.ref().firstSegment());
this.update = update;
this.sqlMapping = context.getDbContext().getSqlMapping(entity);
this.tableName = this.sqlMapping.tableName();
this.toSql = TokenToSQLTransformer.notCollating(context, sqlMapping::columnName, entity, this.tableName, params,
DocStoreUtils::valueToParamCastExpression);
}
@SuppressWarnings("unchecked")
@VisibleForTesting
static void flattenDataEntries(Map entries, Map parameters, String prefix) {
parameters.putAll(entries.keySet().stream().filter(key -> !(entries.get(key) instanceof Map))
.collect(Collectors.toMap(key -> buildKey(prefix, key), key -> valueToDocStoreType(entries.get(key)))));
entries.keySet().stream().filter(key -> entries.get(key) instanceof Map).forEach(
key -> flattenDataEntries((Map) entries.get(key), parameters, buildKey(prefix, key)));
}
private static String buildKey(String prefix, String key) {
return prefix.isEmpty() ? key : prefix + "." + key;
}
static String quote(String key) {
if (key.contains(".")) {
return Arrays.stream(key.split("\\.")).map(element -> DOUBLEQUOTE + element + DOUBLEQUOTE)
.collect(Collectors.joining("."));
}
return DOUBLEQUOTE + key + DOUBLEQUOTE;
}
private static ParamType valueToDocStoreType(Object value) {
String canonicalClassName = value.getClass().getCanonicalName();
if (canonicalClassName.equals("java.lang.Boolean"))
return new ParamType(CdsBaseType.BOOLEAN,
DocStoreUtils.hanaDocStoreTypeFromCdsBaseType(CdsBaseType.BOOLEAN));
if (canonicalClassName.equals("java.lang.Float") || canonicalClassName.equals("java.lang.Double")
|| canonicalClassName.equals("java.math.BigDecimal"))
return new ParamType(CdsBaseType.DOUBLE, DocStoreUtils.hanaDocStoreTypeFromCdsBaseType(CdsBaseType.DOUBLE));
if (canonicalClassName.equals("java.lang.Integer") || canonicalClassName.equals("java.lang.Long"))
return new ParamType(CdsBaseType.INTEGER64,
DocStoreUtils.hanaDocStoreTypeFromCdsBaseType(CdsBaseType.INTEGER64));
return new ParamType(CdsBaseType.STRING, "NVARCHAR");
}
@Override
public SQLStatementBuilder.SQLStatement build() {
// flat map for parameters
Map flattenedParameterMap = new HashMap<>();
// relying on the fact that all entries have the same structure
flattenDataEntries(update.data(), flattenedParameterMap, "");
flattenedParameterMap.keySet().forEach(param -> params
.add(new PreparedCqnStmt.DataParam(param, flattenedParameterMap.get(param).cdsBaseType)));
Stream.Builder builder = Stream.builder();
builder.add("UPDATE");
builder.add(tableName);
builder.add("SET");
builder.add(getSetStringWithParameterMarkers(flattenedParameterMap));
update.where().map(toSql::toSQL).ifPresent(whereSql -> {
builder.add("WHERE");
builder.add(whereSql);
});
String sql = builder.build().collect(joining());
return new SQLStatementBuilder.SQLStatement(sql, params);
}
private String getSetStringWithParameterMarkers(Map flattenedParameterMap) {
return flattenedParameterMap.keySet().stream()
.map(key -> "%s = CAST(? AS %s)".formatted(quote(key), flattenedParameterMap.get(key).hanaType))
.collect(Collectors.joining(", "));
}
@VisibleForTesting
static class ParamType {
private final CdsBaseType cdsBaseType;
private final String hanaType;
ParamType(CdsBaseType cdsBaseType, String hanaType) {
this.cdsBaseType = cdsBaseType;
this.hanaType = hanaType;
}
public CdsBaseType getCdsBaseType() {
return cdsBaseType;
}
public String getHanaType() {
return hanaType;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof ParamType))
return false;
ParamType paramType = (ParamType) o;
return cdsBaseType == paramType.cdsBaseType && hanaType.equals(paramType.hanaType);
}
@Override
public int hashCode() {
return Objects.hash(cdsBaseType, hanaType);
}
}
}