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

com.sap.cds.jdbc.hana.HanaStatementResolver Maven / Gradle / Ivy

There is a newer version: 3.6.1
Show newest version
/************************************************************************
 * © 2020-2024 SAP SE or an SAP affiliate company. All rights reserved. *
 ************************************************************************/
package com.sap.cds.jdbc.hana;

import static com.sap.cds.DataStoreConfiguration.IGNORE_LOCALE_ON_HANA;
import static com.sap.cds.impl.sql.SQLHelper.commaSeparated;
import static com.sap.cds.jdbc.hana.HanaDbContext.NO_USE_HEX_PLAN;
import static com.sap.cds.jdbc.hana.HanaDbContext.USE_HEX_PLAN;
import static com.sap.cds.ql.impl.SelectBuilder.COLLATING;
import static com.sap.cds.ql.impl.SelectBuilder.COLLATING_OFF;
import static com.sap.cds.util.CqnStatementUtils.hasLimit;
import static java.util.stream.Collectors.joining;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sap.cds.CdsException;
import com.sap.cds.DataStoreConfiguration;
import com.sap.cds.impl.localized.LocaleUtils;
import com.sap.cds.impl.sql.SQLHelper;
import com.sap.cds.jdbc.spi.StatementResolver;
import com.sap.cds.ql.Select;
import com.sap.cds.ql.StructuredType;
import com.sap.cds.ql.cqn.CqnLock.Mode;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnSortSpecification;

public class HanaStatementResolver implements StatementResolver {
	private static final Logger logger = LoggerFactory.getLogger(HanaStatementResolver.class);
	private static final Properties COLLATIONS = loadCollations();

	private final boolean ignoreLocale;
	private final boolean hanaCloud;
	private final boolean optimizeForHexEngine;

	public HanaStatementResolver(DataStoreConfiguration dataStoreConfiguration, int majorVersion,
			boolean optimizeForHexEngine) {
		this.optimizeForHexEngine = optimizeForHexEngine;
		this.hanaCloud = majorVersion >= 4;
		this.ignoreLocale = dataStoreConfiguration.getProperty(IGNORE_LOCALE_ON_HANA, false);
	}

	private static Properties loadCollations() {
		try (InputStream in = HanaStatementResolver.class.getClassLoader()
				.getResourceAsStream("hana/collations.properties")) {
			Properties collations = new Properties();
			collations.load(in);
			return collations;
		} catch (Exception e) {
			throw new CdsException("Failed to load HANA collations");
		}
	}

	@Override
	public Optional collate(CqnSortSpecification o, Locale locale) {
		if (ignoreLocale || locale == null) {
			return Optional.empty();
		}
		return collation("COLLATE ", locale);
	}

	private static Optional collation(String prefix, Locale locale) {
		String collationName = getCollationName(locale);
		if (collationName == null) {
			logger.warn("Missing collation name for locale \"{}\"", locale);
			return Optional.empty();
		}
		return Optional.of(prefix + collationName);
	}

	private static String getCollationName(Locale locale) {
		String language = locale.getLanguage();
		if ("zh".equals(language) || "hr".equals(language)) {
			String collationName = COLLATIONS.getProperty(locale.toLanguageTag());
			if (collationName != null) {
				return collationName;
			}
		}
		return COLLATIONS.getProperty(language);
	}

	@Override
	public Optional statementWideCollation(CqnSelect select, Locale locale) {
		if (ignoreLocale || locale == null) {
			return Optional.empty();
		}
		Map hints = select.hints();
		if (COLLATING_OFF.equals(hints.get(COLLATING))) {
			return Optional.empty();
		}
		if (hints.containsKey(NO_USE_HEX_PLAN)) {
			return Optional.of(parametersLocale(locale));
		}
		if (onHex(hints) && hanaCloud) {
			return collation("WITH COLLATION ", locale);
		}
		return Optional.of(parametersLocale(locale));
	}

	private boolean onHex(Map hints) {
		return optimizeForHexEngine || hints.containsKey(USE_HEX_PLAN);
	}

	@Override
	public boolean supportsStatementWideCollation() {
		return true;
	}

	private static String parametersLocale(Locale locale) {
		return "with parameters('LOCALE' = " + SQLHelper.literal(LocaleUtils.getLocaleString(locale)) + ")";
	}

	/**
	 * HANA Upsert: Updates rows in a table or inserts new rows.
	 * 
	 * https://help.sap.com/docs/HANA_SERVICE_CF/7c78579ce9b14a669c1f3295b0d8ca16/ea8b6773be584203bcd99da76844c5ed.html
	 */
	@Override
	public String upsert(String table, Stream keyColumns, Stream upsertColumns,
			Stream upsertValues) {
		String columns = commaSeparated(upsertColumns);
		String values = commaSeparated(upsertValues);

		return Stream.of("UPSERT", table, columns, "VALUES", values, "WITH PRIMARY KEY").collect(joining(" "));
	}

	@Override
	public String lockMode(Mode mode) {
		final String clause;
		switch (mode) {
			case SHARED:
				if (hanaCloud) {
					clause = "FOR SHARE LOCK";
					break;
				}
			default:
				clause = "FOR UPDATE";
		}
		return clause;
	}

	@Override
	public Optional timeoutClause(int timeoutSeconds) {
		if (timeoutSeconds > 0) {
			return Optional.of("WAIT " + timeoutSeconds);
		} else {
			return Optional.of("NOWAIT");
		}
	}

	@Override
	public Optional hints(Map hints) {
		List hanaHints = hints.entrySet().stream()
				.filter(h -> h.getKey().startsWith("hdb.") && h.getValue().equals(true))
				.map(h -> h.getKey().substring(4)).toList();
		if (hanaHints.isEmpty()) {
			return Optional.empty();
		}

		return Optional.of(hanaHints.stream().collect(Collectors.joining(", ", "WITH HINT(", ")")));
	}

	@Override
	public CqnSelect preOptimize(CqnSelect select) {
		Map hints = select.hints();

		if (onHex(hints) && cantRunOnHex(select, hints)) {
			Select> copy = Select.copy(select);

			hints = new HashMap<>(hints);
			hints.remove(USE_HEX_PLAN);
			hints.put(NO_USE_HEX_PLAN, true);
			copy.hints(hints);

			return copy;
		}

		return select;
	}

	private boolean cantRunOnHex(CqnSelect select, Map hints) {
		if (select.from().isSelect()) {
			CqnSelect inner = select.from().asSelect();

			if (hasLimit(inner)) { // HDBHEX-5028
				if (logger.isDebugEnabled()) {
					logger.debug("Executing on legacy engine as statment has nested limit: {}", select.toJson());
				}
				return true;
			}
		}

		return false;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy