All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.sap.cds.impl.sql.SelectStatementBuilder Maven / Gradle / Ivy
/************************************************************************
* © 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;
}
}