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

com.sap.cds.jdbc.hana.search.HanaSearchResolverUsingContains Maven / Gradle / Ivy

The newest version!
/*******************************************************************
 * © 2024 SAP SE or an SAP affiliate company. All rights reserved. *
 *******************************************************************/
package com.sap.cds.jdbc.hana.search;

import static com.sap.cds.DataStoreConfiguration.SEARCH_MODE_LOCALIZED_ASSOC;
import static com.sap.cds.impl.builder.model.Conjunction.and;
import static com.sap.cds.impl.parser.token.CqnBoolLiteral.TRUE;
import static com.sap.cds.reflect.impl.reader.model.CdsConstants.ANNOTATION_CDS_PERSISTENCE_EXISTS;
import static com.sap.cds.util.CdsSearchUtils.getSearchableElements;
import static com.sap.cds.util.CqnStatementUtils.simplifyPredicate;
import static java.util.stream.Collectors.toSet;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

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

import com.sap.cds.DataStoreConfiguration;
import com.sap.cds.impl.DraftUtils;
import com.sap.cds.impl.builder.model.ElementRefImpl;
import com.sap.cds.impl.builder.model.ListValue;
import com.sap.cds.ql.CQL;
import com.sap.cds.ql.Predicate;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnListValue;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnReference;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnSelectListItem;
import com.sap.cds.ql.cqn.CqnSelectListValue;
import com.sap.cds.ql.cqn.CqnSource;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.impl.SelectBuilder;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.util.CdsModelUtils;
import com.sap.cds.util.CdsSearchUtils;
import com.sap.cds.util.CqnStatementUtils;

public class HanaSearchResolverUsingContains extends HanaSearchResolver {

	private static final Logger logger = LoggerFactory.getLogger(HanaSearchResolverUsingContains.class);

	public HanaSearchResolverUsingContains(DataStoreConfiguration config, CdsModel cdsModel, Locale locale) {
		super(config, cdsModel, locale);
	}

	@Override
	public CqnPredicate resolve(CqnPredicate searchExpression, CdsStructuredType rowType, boolean exactSearch) {
		Collection searchableElementRefs = CdsSearchUtils.searchableElementRefs(rowType);

		return CdsSearchUtils.searchToLikeExpression(searchableElementRefs, searchExpression);
	}

	@Override
	protected CqnPredicate searchToHana(Map containsRefs, CqnPredicate expression) {
		CqnListValue refs = ListValue.of(containsRefs.keySet());
		String searchString = toSearchString(simplifyPredicate(expression), false).searchString();

		return CQL.booleanFunc("CONTAINS", List.of(refs, CQL.val(searchString)));
	}

	@Override
	protected boolean needsPushToSubquery(CdsElement element) {
		return isDeclaredByActiveEntity(element);
	}

	private static boolean isDeclaredByActiveEntity(CdsElement element) {
		CdsStructuredType declaringType = element.getDeclaringType();
		if (isActiveEntity(declaringType)) {
			logger.debug("""
					Fallback to search with LIKE. Entity {} is draft-enabled. This causes a subquery in \
					SQL, which prevents the usage of CONTAINS.\
					""", declaringType);
			return true;
		} else {
			return false;
		}
	}

	private static boolean isActiveEntity(CdsStructuredType targetType) {
		return DraftUtils.isDraftEnabled(targetType) && !DraftUtils.isDraftView(targetType);
	}

	@Override
	public void pushDownSearchToSubquery(CqnSelect select, CqnSelect subquery) {
		CqnPredicate merged = and(subquery.search(), select.search()).orElse(TRUE);

		CqnSource source = subquery.from();
		if (source.isRef()) {
			// innermost
			CdsEntity targetEntity = CdsModelUtils.entity(model, subquery.ref());

			// we only want to search the elements on the select list of the subquery
			Collection resolved = CqnStatementUtils.resolveStar(subquery.items(),
					subquery.excluding(), targetEntity, false);
			Set exposed = resolved.stream().flatMap(CqnSelectListItem::ofRef).map(CqnReference::lastSegment)
					.collect(toSet());
			Set intersection = getSearchableElements(subquery, targetEntity).stream()
					.map(CqnReference::lastSegment).filter(exposed::contains).collect(toSet());

			((SelectBuilder) subquery.asSelect()).search(t -> (Predicate) merged, intersection);
		} else {
			((SelectBuilder) subquery.asSelect()).search(merged);
		}
		((SelectBuilder) select).search((CqnPredicate) null);
	}

	@Override
	protected void handleLargeStringElement(Set like, Map contains,
			CdsStructuredType targetType, CqnElementRef ref, CdsElement element) {
		logger.debug(
				"Searching large string element {} in entity {} using LIKE since CONTAINS is not supported for (N)CLOB columns on SAP HANA",
				ref, targetType);
		like.add(ref);
	}

	@Override
	protected boolean handleLocalizedElement(CdsStructuredType targetType, Set like,
			Map contains, boolean languageGiven,
			CqnElementRef ref, CdsElement element) {
		if (languageGiven) {
			if (isReachableViaLocalizedAssoc(element)) {
				contains.put(ref, element);
				contains.put(localizedRef(ref), element);
				return true; // push to subquery
			} else {
				like.add(ref);
			}
		} else {
			contains.put(ref, element);
		}
		return false;
	}

	@Override
	protected void handleToManyElements(CdsStructuredType targetType, Set like,
			Map contains, boolean languageGiven, CqnElementRef ref,
			CdsElement element) {
		if (element.isLocalized()) {
			like.add(ref);
		} else {
			contains.put(ref, element);
		}
	}

	@Override
	protected void handleRegularElement(CdsStructuredType targetType, Set like,
			Map contains, CqnElementRef ref, CdsElement element) {
		if (targetType instanceof CdsEntity e && isComputed(e, ref)) {
			like.add(ref);
		} else {
			contains.put(ref, element);
		}
	}

	private boolean isComputed(CdsEntity targetEntity, CqnElementRef ref) {

		if (Boolean.TRUE.equals(targetEntity.getAnnotationValue(ANNOTATION_CDS_PERSISTENCE_EXISTS, false))) {
			logger.debug(
					"""
					The searchable ref {} is treated as 'computed' as the targetEntity {} is annotated with \
					{} and we cannot analyze computed refs.\
					""",
					ref, targetEntity, ANNOTATION_CDS_PERSISTENCE_EXISTS);
			return true;
		}

		// only views can have computed fields, target entities can have calculated
		// elements
		if (!targetEntity.isView()) {
			return targetEntity.findElement(ref.path()).map(CdsElement::isCalculated).orElse(false);
		}

		Optional targetQuery = targetEntity.query();
		if (targetQuery.isEmpty()) {
			logger.debug(
					"The searchable ref {} is treated as 'computed' as the targetEntity {} is a view with an unsupported query.",
					ref, targetEntity);
			return true;
		}

		CqnSelect query = targetQuery.get();
		CqnSource source = query.from();
		if (!source.isRef()) {
			logger.debug(
					"""
					The searchable ref {} is treated as 'computed' as the query {} of the targetEntity {} selects \
					from a source which is not a ref {}.\
					""",
					ref, query, targetEntity, source);
			return true;
		}

		String startSegName = ref.firstSegment();
		Optional match = query.items().stream().flatMap(CqnSelectListItem::ofValue)
				.filter(slv -> slv.displayName().equals(startSegName)).findFirst();
		if (match.isPresent()) {
			CqnSelectListValue slv = match.get();
			if (!slv.isRef()) {
				return true;
			}
			ref = concatRefs(slv.asRef(), ref);

		}
		CqnStructuredTypeRef typeRef = query.ref();
		CdsEntity sourceEntity = CdsModelUtils.entity(model, typeRef);

		return isComputed(sourceEntity, ref);
	}

	private static CqnElementRef concatRefs(CqnElementRef prefix, CqnElementRef suffix) {
		int tail = suffix.size();
		List segs = new ArrayList<>(prefix.size() + tail - 1);
		segs.addAll(prefix.segments());
		segs.addAll(suffix.segments().subList(1, tail));
		return ElementRefImpl.elementRef(segs, null, null);
	}

	@Override
	protected String defaultSearchMode() {
		return SEARCH_MODE_LOCALIZED_ASSOC;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy