com.sap.cds.jdbc.hana.HanaStatementResolver Maven / Gradle / Ivy
/************************************************************************
* © 2020-2023 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.ql.impl.SelectBuilder.COLLATING_HANA;
import static com.sap.cds.ql.impl.SelectBuilder.COLLATING_HANA_OFF;
import static com.sap.cds.ql.impl.SelectBuilder.COLLATING_HANA_WITH_COLLATION;
import static java.util.stream.Collectors.joining;
import java.io.InputStream;
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.cqn.CqnLock.Mode;
import com.sap.cds.ql.cqn.CqnSortSpecification;
import com.sap.cds.ql.cqn.CqnStatement;
public class HanaStatementResolver implements StatementResolver {
private static final Logger logger = LoggerFactory.getLogger(HanaStatementResolver.class);
private static final Properties COLLATIONS = new Properties();
private final boolean ignoreLocale;
private final boolean hanaCloud;
public HanaStatementResolver(DataStoreConfiguration dataStoreConfiguration, int majorVersion) {
this.ignoreLocale = dataStoreConfiguration.getProperty(IGNORE_LOCALE_ON_HANA, false);
this.hanaCloud = majorVersion >= 4;
if (hanaCloud) {
loadCollations();
}
}
private static void loadCollations() {
try (InputStream in = HanaStatementResolver.class.getClassLoader().getResourceAsStream("hana/collations.properties")) {
COLLATIONS.load(in);
} catch (Exception e) {
throw new CdsException("Failed to load HANA collations");
}
}
@Override
public Optional collate(CqnSortSpecification o, Locale locale) {
if (locale != null) {
return collation("COLLATE ", locale);
}
return Optional.empty();
}
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 withCollation(CqnStatement statement, Locale locale) {
String collationType = (String) statement.hints().get(COLLATING_HANA);
if (ignoreLocale || locale == null || COLLATING_HANA_OFF.equals(collationType)) {
return Optional.empty();
}
if (COLLATING_HANA_WITH_COLLATION.equals(collationType) && supportsWithCollation(statement)) {
return collation("WITH COLLATION ", locale);
}
return Optional.of(parametersLocale(locale));
}
private boolean supportsWithCollation(CqnStatement statement) {
return hanaCloud && statement.isSelect();
}
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.keySet().stream().filter(h -> h.startsWith("hdb.")).map(h -> h.substring(4)).collect(Collectors.toList());
if (hanaHints.isEmpty()) {
return Optional.empty();
}
return Optional.of(hanaHints.stream().collect(Collectors.joining(", ", "WITH HINT(", ")")));
}
}