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

com.sap.cds.ql.impl.SelectBuilder Maven / Gradle / Ivy

There is a newer version: 3.6.1
Show newest version
/*******************************************************************
 * © 2019 SAP SE or an SAP affiliate company. All rights reserved. *
 *******************************************************************/
package com.sap.cds.ql.impl;

import static com.sap.cds.impl.builder.model.StructuredTypeImpl.structuredType;
import static com.sap.cds.ql.impl.SelectListValueBuilder.refToSlv;
import static com.sap.cds.ql.impl.SelectListValueBuilder.select;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;

import com.fasterxml.jackson.databind.node.ArrayNode;
import com.sap.cds.impl.builder.model.CqnParam;
import com.sap.cds.impl.builder.model.ElementRefImpl;
import com.sap.cds.impl.builder.model.ExpressionImpl;
import com.sap.cds.impl.builder.model.LiteralImpl;
import com.sap.cds.impl.builder.model.LockImpl;
import com.sap.cds.impl.builder.model.Negation;
import com.sap.cds.impl.builder.model.ScalarFunctionCall;
import com.sap.cds.impl.builder.model.SearchPredicate;
import com.sap.cds.impl.builder.model.StructuredTypeImpl;
import com.sap.cds.impl.builder.model.StructuredTypeProxy;
import com.sap.cds.impl.builder.model.StructuredTypeRefImpl;
import com.sap.cds.impl.builder.model.SubQuery;
import com.sap.cds.impl.parser.builder.LimitBuilder;
import com.sap.cds.impl.parser.builder.SortSpecBuilder;
import com.sap.cds.impl.parser.token.Jsonizer;
import com.sap.cds.ql.FunctionCall;
import com.sap.cds.ql.Literal;
import com.sap.cds.ql.Parameter;
import com.sap.cds.ql.Predicate;
import com.sap.cds.ql.Searchable;
import com.sap.cds.ql.Select;
import com.sap.cds.ql.Source;
import com.sap.cds.ql.StructuredType;
import com.sap.cds.ql.cqn.CqnLimit;
import com.sap.cds.ql.cqn.CqnLiteral;
import com.sap.cds.ql.cqn.CqnLock;
import com.sap.cds.ql.cqn.CqnModifier;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnSelectListItem;
import com.sap.cds.ql.cqn.CqnSortSpecification;
import com.sap.cds.ql.cqn.CqnSource;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnValue;

public class SelectBuilder> extends Select {

	private final List columns = new ArrayList<>();
	private final Set searchableElements = new HashSet<>();
	private final List excluding = new ArrayList<>();
	private final List groupBy = new ArrayList<>();
	private final List orderBy = new ArrayList<>();
	private final Source source;

	private boolean distinct;
	private boolean count;
	private CqnPredicate where;
	private CqnPredicate having;
	private CqnLimit limit;
	private CqnPredicate search;
	private CqnLock rowLock;

	private SelectBuilder(Source source) {
		this.source = source;
	}

	public static > SelectBuilder from(Class entity) {
		return new SelectBuilder<>(StructuredTypeProxy.create(entity));
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static SelectBuilder> from(CqnStructuredTypeRef ref) {
		return new SelectBuilder(structuredType(ref));
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static Select> from(CqnSelect select) {
		SubQuery sq = new SubQuery(select);
		return new SelectBuilder(sq);
	}

	@SuppressWarnings("unchecked")
	public static SelectBuilder from(CqnSource source) {
		if (source.isRef()) {
			StructuredType structuredType = structuredType(source.asRef());
			return new SelectBuilder<>(structuredType);
		} else if (source.isSelect()) {
			return new SelectBuilder<>((Source) source);
		} else if (source.isJoin()) {
			Source query = (Source) source;
			return new SelectBuilder<>(query);
		}

		throw new IllegalStateException("unexpected source type " + source.getClass().getName());
	}

	public static , R extends StructuredType> SelectBuilder from(Class type,
			Function path) {
		return new SelectBuilder<>(path.apply(StructuredTypeProxy.create(type)));
	}

	public static > SelectBuilder from(Source source) {
		return new SelectBuilder<>(source);
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static SelectBuilder> from(String entityName) {
		return new SelectBuilder(structuredType(entityName));
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static SelectBuilder> from(String entity, UnaryOperator> path) {
		return new SelectBuilder(path.apply(structuredType(entity)));
	}

	public static SelectBuilder> copy(CqnSelect select) {
		return copy(select, ExpressionVisitor.COPY);
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static SelectBuilder> copy(CqnSelect select, CqnModifier modifier) {
		SelectBuilder copy = SelectBuilder.from(copy(select.from(), modifier));

		copy.distinct = modifier.distinct(select.isDistinct());
		copy.count = modifier.inlineCount(select.hasInlineCount());
		copy.where = modifier.where(ExpressionVisitor.copy(select.where(), modifier));
		copy.having = modifier.having(ExpressionVisitor.copy(select.having(), modifier));
		copy.search = modifier.search(ExpressionVisitor.copy(select.search(), modifier));
		copy.searchableElements
				.addAll(modifier.searchableElements(new HashSet<>(((SelectBuilder) select).searchableElements)));

		select.limit().ifPresent(limit -> copy.limit = modifier
				.limit(LimitBuilder.rows(limit.top()).offset(limit.skip())));
		select.getLock().ifPresent(lock -> copy.rowLock = lock);

		Set excluding = modifier.excluding(new HashSet<>(select.excluding()));
		List items = modifier.items(ExpressionVisitor.copy(select.items(), modifier));
		List groupBy = modifier.groupBy(ExpressionVisitor.copy(select.groupBy(), modifier));
		List orderBy = modifier
				.orderBy(select.orderBy().stream().map(c -> ExpressionVisitor.copy(c, modifier)).collect(toList()));

		return copy.columns(items).excluding(excluding).groupBy(groupBy).orderBy(orderBy);
	}

	private static CqnSource copy(CqnSource source, CqnModifier modifier) {
		if (source.isRef()) {
			return ExpressionVisitor.copy(source.asRef(), modifier);
		}
		if (source.isSelect()) {
			CqnSelect inner = source.asSelect();

			return new SubQuery(SelectBuilder.copy(inner, modifier));
		}
		throw new UnsupportedOperationException("Joins are not supported as source of SELECT");
	}

	@Override
	public Source from() {
		return source;
	}

	@Override
	public CqnStructuredTypeRef ref() {
		return StructuredTypeRefImpl.typeRef(source);
	}

	@Override
	public SelectBuilder distinct() {
		return distinct(true);
	}

	public SelectBuilder distinct(boolean distinct) {
		this.distinct = distinct;
		return this;
	}

	@Override
	public boolean isDistinct() {
		return distinct;
	}

	@Override
	public SelectBuilder inlineCount() {
		return count(true);
	}

	public SelectBuilder count(boolean count) {
		this.count = count;
		return this;
	}

	@Override
	public boolean hasInlineCount() {
		return count;
	}

	@Override
	public SelectBuilder columns(List items) {
		return columns(items.stream());
	}

	@Override
	public SelectBuilder columns(Stream items) {
		columns.clear();
		items.forEach(this::columns);

		return this;
	}

	private SelectBuilder columns(CqnSelectListItem item) {
		columns.add(refToSlv(item));

		return this;
	}

	@Override
	public SelectBuilder columns(String... elements) { // NOSONAR
		return columns(stream(elements).map(e -> select(e).build()));
	}

	public SelectBuilder addItem(CqnSelectListItem item) {
		columns.add(item);

		return this;
	}

	public Select columns(CqnLiteral literal) {
		return columns(select(literal).as("val").build());
	}

	@Override
	public List items() {
		return Collections.unmodifiableList(columns);
	}

	@Override
	public SelectBuilder excluding(Collection slis) {
		excluding.clear();
		excluding.addAll(slis);

		return this;
	}

	public SelectBuilder addExclude(String slis) {
		excluding.add(slis);

		return this;
	}

	@Override
	public List excluding() {
		return Collections.unmodifiableList(excluding);
	}

	@Override
	public SelectBuilder where(Function pred) {
		return where(pred.apply(getType()));
	}

	@Override
	public SelectBuilder where(CqnPredicate pred) {
		where = pred;

		return this;
	}

	public SelectBuilder where(Optional optionalWhere) {
		where = optionalWhere.orElse(null);

		return this;
	}

	@Override
	public SelectBuilder matching(Map values) {
		return where(ExpressionImpl.matching(values));
	}

	@Override
	public SelectBuilder byId(Object idValue) {
		return where(ExpressionImpl.byId(idValue));
	}

	@Override
	public SelectBuilder search(String searchTerm) {
		return search(searchTerm, Collections.emptyList());
	}

	@Override
	public SelectBuilder search(String searchTerm, Iterable searchableElements) {
		return search(searchable -> searchable.has(searchTerm), searchableElements);
	}

	@Override
	public SelectBuilder search(Function pred) {
		return search(pred, Collections.emptyList());
	}

	@Override
	public SelectBuilder search(Function pred, Iterable searchableElements) {
		this.searchableElements.clear();
		searchableElements.forEach(this.searchableElements::add);
		search = pred.apply(SearchPredicate::new);

		return this;
	}

	@Override
	public SelectBuilder search(CqnPredicate predicate) {
		search = predicate;

		return this;
	}

	public Collection searchableElements() {
		return Collections.unmodifiableCollection(searchableElements);
	}

	@Override
	public Optional where() {
		return Optional.ofNullable(where);
	}

	@Override
	public Optional search() {
		return Optional.ofNullable(search);
	}

	@Override
	public Select lock(int timeout) {
		this.rowLock = new LockImpl(timeout);
		return this;
	}

	@Override
	public Select lock() {
		this.rowLock = new LockImpl();
		return this;
	}

	public Select lockSelectedRows(CqnLock rowLock) {
		this.rowLock = rowLock;
		return this;
	}

	@Override
	public Optional getLock() {
		return Optional.ofNullable(rowLock);
	}

	@Override
	public SelectBuilder groupBy(List items) {
		groupBy.clear();
		groupBy.addAll(items);

		return this;
	}

	@Override
	public Select groupBy(String... refs) {
		groupBy.clear();
		Arrays.stream(refs).map(ElementRefImpl::parse).forEach(groupBy::add);

		return this;
	}

	@Override
	public List groupBy() {
		return Collections.unmodifiableList(groupBy);
	}

	@Override
	public SelectBuilder having(Function pred) {
		return having(pred.apply(getType()));
	}

	@Override
	public SelectBuilder having(CqnPredicate pred) {
		having = pred;

		return this;
	}

	public SelectBuilder having(Optional pred) {
		having = pred.orElse(null);

		return this;
	}

	@Override
	public Optional having() {
		return Optional.ofNullable(having);
	}

	@Override
	public final SelectBuilder orderBy(List sortSpec) {
		orderBy.clear();
		orderBy.addAll(sortSpec);

		return this;
	}

	@Override
	public List orderBy() {
		return Collections.unmodifiableList(orderBy);
	}

	@Override
	public Select orderBy(String... refs) {
		orderBy.clear();
		for (String ref : refs) {
			orderBy.add(SortSpecBuilder.by(ElementRefImpl.parse(ref)).build());
		}

		return this;
	}

	@Override
	public SelectBuilder limit(long rows, long offset) {
		return limit(LimitBuilder.rows(rows).offset(offset).build());
	}

	public SelectBuilder limit(CqnLimit limit) {
		this.limit = limit;

		return this;
	}

	public SelectBuilder limit(Optional optionalLimit) {
		this.limit = optionalLimit.orElse(null);

		return this;
	}

	@Override
	public Optional limit() {
		return Optional.ofNullable(limit);
	}

	@Override
	public T getType() {
		return source.getType();
	}

	public static  Parameter param() {
		return CqnParam.param();
	}

	public static  Parameter param(String name) {
		return CqnParam.param(name);
	}

	public static  FunctionCall func(String functionName, CqnValue... args) {
		return ScalarFunctionCall.create(functionName, args);
	}

	public static  Literal literal(U val) {
		return LiteralImpl.literal(val);
	}

	public static Predicate not(Predicate p) {
		return Negation.not(p);
	}

	@Override
	public String toJson() {
		Jsonizer cqn = Jsonizer.object("from", source);
		if (isDistinct()) {
			cqn.put("distinct", true);
		}
		if (hasInlineCount()) {
			cqn.put("count", true);
		}
		if (!columns.isEmpty()) {
			ArrayNode cols = cqn.array("columns");
			columns.forEach(cols::addPOJO);
		}
		if (!excluding.isEmpty()) {
			ArrayNode excl = cqn.array("excluding");
			excluding.forEach(excl::add);
		}
		where().ifPresent(w -> {
			ArrayNode whereArr = cqn.array("where");
			w.tokens().forEach(whereArr::addPOJO);
		});
		if (!groupBy.isEmpty()) {
			ArrayNode groupByArray = cqn.array("groupBy");
			groupBy.forEach(groupByArray::addPOJO);
		}
		having().ifPresent(h -> {
			ArrayNode havingArr = cqn.array("having");
			h.tokens().forEach(havingArr::addPOJO);
		});
		if (!orderBy.isEmpty()) {
			ArrayNode orderByArray = cqn.array("orderBy");
			orderBy.forEach(orderByArray::addPOJO);
		}
		limit().ifPresent(l -> cqn.put("limit", l));
		search().ifPresent(s -> cqn.put("search", s));
		getLock().ifPresent(f -> cqn.put("forUpdate", f.timeout().isPresent() ? f : Jsonizer.empty()));

		return Jsonizer.object("SELECT", cqn).toJson();
	}

	public CqnSelect build() {
		return this;
	}

}