All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cds.impl.docstore.DocStoreUpdateStatementBuilder Maven / Gradle / Ivy

There is a newer version: 3.6.1
Show newest version
/************************************************************************
 * © 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 static com.sap.cds.impl.sql.UpdateStatementBuilder.commaSeparated;

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);
	}

	@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");
		Stream elements = setClauses(flattenedParameterMap);
		builder.add(commaSeparated(elements));

		update.where().map(w -> toSQL.toSQL(entity, w)).ifPresent(s -> {
			builder.add("WHERE");
			builder.add(s);
		});

		String sql = builder.build().collect(joining());

		return new SQLStatementBuilder.SQLStatement(sql, params);
	}

	private Stream setClauses(Map flattenedParameterMap) {
		return flattenedParameterMap.keySet().stream()
				.map(key -> "%s = CAST(? AS %s)".formatted(quote(key), flattenedParameterMap.get(key).hanaType));
	}

	@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);
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy