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

com.sap.cds.impl.sql.SelectStatementBuilder Maven / Gradle / Ivy

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

import static com.sap.cds.impl.sql.SQLStatementBuilder.commaSeparated;
import static com.sap.cds.impl.sql.SpaceSeparatedCollector.joining;
import static com.sap.cds.util.CdsModelUtils.entity;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Stream;

import com.google.common.collect.Streams;
import com.sap.cds.SessionContext;
import com.sap.cds.impl.Context;
import com.sap.cds.impl.PreparedCqnStmt.Parameter;
import com.sap.cds.impl.builder.model.Conjunction;
import com.sap.cds.impl.localized.LocaleUtils;
import com.sap.cds.impl.qat.FromClauseBuilder;
import com.sap.cds.impl.qat.QatBuilder;
import com.sap.cds.impl.qat.QatSelectableNode;
import com.sap.cds.impl.qat.Ref2QualifiedColumn;
import com.sap.cds.jdbc.spi.StatementResolver;
import com.sap.cds.ql.cqn.CqnLock;
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;
import com.sap.cds.reflect.CdsEntity;

public class SelectStatementBuilder implements SQLStatementBuilder {

	private final Context context;
	private final CqnSelect select;
	private final Deque outer;
	private final StatementResolver statementResolver;
	private final SessionContext sessionContext;
	private final List params;

	private Ref2QualifiedColumn aliasResolver;
	private TokenToSQLTransformer toSQL;
	private final LocaleUtils localeUtils;
	private final CdsEntity targetEntity;

	public SelectStatementBuilder(Context context, List params, CqnSelect select,
			Deque outer) {
		this.context = context;
		this.params = params;
		this.select = select;
		this.outer = append(outer, new QatBuilder(context, select, outer.size()).create());
		this.statementResolver = context.getDbContext().getStatementResolver();
		this.sessionContext = context.getSessionContext();
		this.localeUtils = new LocaleUtils(context.getCdsModel(), context.getDataStoreConfiguration());
		this.targetEntity = select.from().isRef() ? entity(context.getCdsModel(), select.from().asRef()) : null;
	}

	public SQLStatement build() {
		aliasResolver = new Ref2QualifiedColumn(context.getDbContext()::getSqlMapping, outer,localeUtils);
		toSQL = new TokenToSQLTransformer(context, params, aliasResolver, outer);
		List snippets = new ArrayList<>();
		with().forEach(snippets::add);
		snippets.add("SELECT");
		distinct().forEach(snippets::add);
		columns().forEach(snippets::add);
		from().forEach(snippets::add);
		where().forEach(snippets::add);
		groupBy().forEach(snippets::add);
		having().forEach(snippets::add);
		orderBy().forEach(snippets::add);
		limit().forEach(snippets::add);
		lock().forEach(snippets::add);
		hints().forEach(snippets::add);
		String sql = snippets.stream().collect(joining());

		return new SQLStatement(sql, params);
	}

	private Stream distinct() {
		if (select.isDistinct()) {
			return Stream.of("DISTINCT");
		}

		return Stream.empty();
	}

	private Stream columns() {
		List items = select.items();
		if (items.isEmpty()) {
			throw new IllegalStateException("select * not expected");
		}

		Stream.Builder sql = Stream.builder();
		items.stream().flatMap(CqnSelectListItem::ofValue).forEach(slv -> {
			sql.add(",");
			sql.add(toSQL.apply(slv.value()));
			slv.alias().ifPresent(a -> {
				sql.add("as");
				sql.add(SQLHelper.delimited(a));
			});
		});

		return sql.build().skip(1);
	}

	private Stream with() {
		FromClauseBuilder fromClauseBuilder = new FromClauseBuilder(context, params, select.hints());

		return fromClauseBuilder.with(outer);
	}

	private Stream from() {
		FromClauseBuilder fromClauseBuilder = new FromClauseBuilder(context, params, select.hints());

		return Streams.concat(Stream.of("FROM"), fromClauseBuilder.sql(outer));
	}

	private Stream where() {
		Optional where = Conjunction.and(select.where(), select.search());
		CqnSource source = select.from();
		if (source.isRef()) {
			CqnStructuredTypeRef ref = source.asRef();
			where = Conjunction.and(where, ref.targetSegment().filter());
		}

		Locale locale = sessionContext.getLocale();
		statementResolver.collate(locale).ifPresent(aliasResolver::startCollate);
		Stream.Builder sql = Stream.builder();
		where.map(toSQL::toSQL).ifPresent(s -> {
			sql.add("WHERE");
			sql.add(s);
		});
		aliasResolver.stopCollate();

		return sql.build();
	}

	private Stream groupBy() {
		List groupByTokens = select.groupBy();
		if (groupByTokens.isEmpty()) {
			return Stream.empty();
		}
		return Streams.concat(Stream.of("GROUP BY"), commaSeparated(groupByTokens.stream(), toSQL::apply));
	}

	private Stream having() {
		Optional having = select.having();

		Locale locale = sessionContext.getLocale();
		statementResolver.collate(locale).ifPresent(aliasResolver::startCollate);
		Stream.Builder sql = Stream.builder();
		having.map(toSQL::toSQL).ifPresent(s -> {
			sql.add("HAVING");
			sql.add(s);
		});
		aliasResolver.stopCollate();

		return sql.build();
	}

	private Stream orderBy() {
		List orderBy = select.orderBy();
		if (orderBy.isEmpty()) {
			return Stream.empty();
		}
		return Streams.concat(Stream.of("ORDER BY"), commaSeparated(orderBy.stream(), this::sort));
	}

	private String sort(CqnSortSpecification o) {
		StringBuilder sort = new StringBuilder(toSQL.apply(o.value()));
		Optional collateClause = statementResolver.collate(o, sessionContext.getLocale());
		if (collateClause.isPresent() && requiresCollate(o.value())) {
			sort.append(" " + collateClause.get());
		}
		switch (o.order()) {
		case DESC:
			sort.append(" DESC NULLS LAST");
			break;
		case DESC_NULLS_FIRST:
			sort.append(" DESC NULLS FIRST");
			break;
		case ASC_NULLS_LAST:
			sort.append(" NULLS LAST");
			break;
		default: // ASC
			sort.append(" NULLS FIRST");
		}
		return sort.toString();
	}

	private boolean requiresCollate(CqnValue value) {
		if (targetEntity != null) {
			if (value.isRef()) {
				return localeUtils.requiresCollate(targetEntity, value.asRef());
			}
			if (value.isFunction()) {
				// could require collate: add if argument requires collate
				return value.asFunction().args().stream().anyMatch(this::requiresCollate);
			}
		}
		return false;
	}

	private Stream limit() {
		Stream.Builder sql = Stream.builder();
		select.limit().ifPresent(l -> {
			sql.add("LIMIT");
			l.limit().tokens().map(toSQL).forEach(sql::add);
			l.offset().ifPresent(o -> {
				sql.add("OFFSET");
				o.tokens().map(toSQL).forEach(sql::add);
			});
		});

		return sql.build();
	}

	private Stream lock() {
		// Java 9: stream = select.getLock().stream();
		Stream.Builder sb = Stream.builder();
		select.getLock().ifPresent(sb::add);
		Stream stream = sb.build();

		return stream.flatMap(statementResolver::lockClause);
	}

	private Stream hints() {
		Stream.Builder sql = Stream.builder();
		statementResolver.hints(select.hints()).ifPresent(sql::add);

		return sql.build();
	}

	private static Deque append(Deque prefix, QatSelectableNode newRoot) {
		ArrayDeque roots = new ArrayDeque<>(prefix);
		roots.add(newRoot);

		return roots;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy