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

com.sap.cds.impl.InlineCountProcessorFactory Maven / Gradle / Ivy

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

import static com.sap.cds.DataStoreConfiguration.INLINE_COUNT;
import static com.sap.cds.DataStoreConfiguration.INLINE_COUNT_AUTO;
import static com.sap.cds.DataStoreConfiguration.INLINE_COUNT_QUERY;
import static com.sap.cds.DataStoreConfiguration.INLINE_COUNT_WINDOW_FUNCTION;

import java.util.List;
import java.util.Map;

import com.google.common.annotations.VisibleForTesting;
import com.sap.cds.DataStoreConfiguration;
import com.sap.cds.Result;
import com.sap.cds.ResultBuilder;
import com.sap.cds.ql.CQL;
import com.sap.cds.ql.Select;
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.Modifier;
import com.sap.cds.ql.impl.SelectBuilder;
import com.sap.cds.reflect.CdsBaseType;
import com.sap.cds.util.CqnStatementUtils.Count;

public class InlineCountProcessorFactory {

	private final CdsDataStoreImpl dataStore;
	private final DataStoreConfiguration cfg;

	InlineCountProcessorFactory(CdsDataStoreImpl dataStore, DataStoreConfiguration cfg) {
		this.dataStore = dataStore;
		this.cfg = cfg;
	}

	private static final InlineCountProcessor noOp = new InlineCountProcessor() {
	};

	private static final InlineCountProcessor rowCounter = new InlineCountProcessor() {
		@Override
		public ResultBuilder execute(ResultBuilder result, Map paramValues) {
			result.inlineCount(result.rowCount());

			return result;
		}
	};

	public InlineCountProcessor create(CqnSelect query) {
		if (!query.hasInlineCount()) {
			return noOp;
		}

		if (!query.hasLimit()) {
			return rowCounter;
		}

		if (query.top() == 0) {
			return new QueryProcessor(query);
		}

		String inlineCountConfig = cfg.getProperty(INLINE_COUNT, INLINE_COUNT_AUTO);

		return switch (inlineCountConfig) {
			case INLINE_COUNT_QUERY -> new QueryProcessor(query);
			case INLINE_COUNT_WINDOW_FUNCTION -> new WindowFunctionProcessor(query);
			default -> !hasFilter(query) ? new QueryProcessor(query) // count(*) is very quick if there's no filter
					: new WindowFunctionProcessor(query);
		};
	}

	private static boolean hasFilter(CqnSelect query) {
		return query.where().isPresent() || query.search().isPresent()
				|| query.from().isRef() && query.ref().targetSegment().filter().isPresent();

	}

	private class QueryProcessor implements InlineCountProcessor {
		private CqnSelect select;

		QueryProcessor(CqnSelect select) {
			this.select = select;
		}

		@Override
		public ResultBuilder execute(ResultBuilder result, Map paramValues) {
			long rowCount = result.rowCount();
			if (requiresInlineCountQuery(select.top(), select.skip(), rowCount)) {
				result.inlineCount(executeInlineCountQuery(select, paramValues));
			} else {
				result.inlineCount(rowCount);
			}

			return result;
		}

		private long executeInlineCountQuery(CqnSelect select, Map paramValues) {
			CqnSelect inlineCountQuery = inlineCountQuery(select);
			Result result = dataStore.executeResolvedQuery(inlineCountQuery, paramValues).result();

			return result.single(Count.class).getCount();
		}

		private static CqnSelect inlineCountQuery(CqnSelect select) {
			Select countQuery = SelectBuilder.from(select.from());
			select.where().ifPresent(countQuery::where);
			select.search().ifPresent(countQuery::search);
			select.having().ifPresent(countQuery::having);
			if (select.isDistinct() || !select.groupBy().isEmpty()) {
				countQuery.columns(select.items());
				countQuery.groupBy(select.groupBy());
				countQuery.distinct();
				countQuery = Select.from(countQuery);
			}

			return countQuery.columns(Count.ALL);
		}

	}

	private class WindowFunctionProcessor implements InlineCountProcessor {

		private static final String $COUNT = "_$count";
		private static final CqnSelectListValue COUNT_ALL_OVER = CQL.plain("COUNT(*) OVER()").type(CdsBaseType.INT64)
				.as($COUNT);
		private final CqnSelect select;

		WindowFunctionProcessor(CqnSelect select) {
			this.select = select;
		}

		@Override
		public CqnSelect prepare(CqnSelect select) {
			return CQL.copy(select, new Modifier() {
				@Override
				public List items(List items) {
					items.add(COUNT_ALL_OVER);

					return items;
				}
			});
		}

		@Override
		public ResultBuilder execute(ResultBuilder result, Map paramValues) {
			if (result.rowCount() == 0 && select.skip() > 0) {
				// all rows skipped. Fallback to query
				return new QueryProcessor(select).execute(result, paramValues);
			}
			Result tmpResult = result.result();
			long inlineCount = tmpResult.first().map(r -> (Long) r.get($COUNT)).orElse(0l);
			result.inlineCount(inlineCount);
			tmpResult.stream().forEach(r -> r.remove($COUNT));

			return result;
		}
	}

	@VisibleForTesting
	static boolean requiresInlineCountQuery(long top, long skip, long rowCount) {
		return skip > 0 || top <= rowCount;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy