org.springframework.data.cassandra.core.StatementFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spring-data-cassandra Show documentation
Show all versions of spring-data-cassandra Show documentation
Cassandra support for Spring Data
/*
* Copyright 2017-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.data.cassandra.core.convert.QueryMapper;
import org.springframework.data.cassandra.core.convert.UpdateMapper;
import org.springframework.data.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.cassandra.core.cql.QueryOptions;
import org.springframework.data.cassandra.core.cql.QueryOptionsUtil;
import org.springframework.data.cassandra.core.cql.WriteOptions;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.core.query.Columns;
import org.springframework.data.cassandra.core.query.Columns.ColumnSelector;
import org.springframework.data.cassandra.core.query.Columns.FunctionCall;
import org.springframework.data.cassandra.core.query.Columns.Selector;
import org.springframework.data.cassandra.core.query.CriteriaDefinition;
import org.springframework.data.cassandra.core.query.CriteriaDefinition.Predicate;
import org.springframework.data.cassandra.core.query.Filter;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.core.query.Update;
import org.springframework.data.cassandra.core.query.Update.AddToMapOp;
import org.springframework.data.cassandra.core.query.Update.AddToOp;
import org.springframework.data.cassandra.core.query.Update.AddToOp.Mode;
import org.springframework.data.cassandra.core.query.Update.AssignmentOp;
import org.springframework.data.cassandra.core.query.Update.IncrOp;
import org.springframework.data.cassandra.core.query.Update.RemoveOp;
import org.springframework.data.cassandra.core.query.Update.SetAtIndexOp;
import org.springframework.data.cassandra.core.query.Update.SetAtKeyOp;
import org.springframework.data.cassandra.core.query.Update.SetOp;
import org.springframework.data.convert.EntityWriter;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.ProjectionInformation;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import com.datastax.driver.core.RegularStatement;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.querybuilder.Assignment;
import com.datastax.driver.core.querybuilder.Clause;
import com.datastax.driver.core.querybuilder.Delete;
import com.datastax.driver.core.querybuilder.Ordering;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.core.querybuilder.Select.Selection;
import com.datastax.driver.core.querybuilder.Select.SelectionOrAlias;
import com.google.common.primitives.Ints;
/**
* Statement factory to render {@link Statement} from {@link Query} and {@link Update} objects.
*
* @author Mark Paluch
* @author John Blum
* @see Query
* @see Update
* @since 2.0
*/
public class StatementFactory {
private final QueryMapper queryMapper;
private final UpdateMapper updateMapper;
private final ProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
/**
* Create {@link StatementFactory} given {@link UpdateMapper}.
*
* @param updateMapper must not be {@literal null}.
*/
public StatementFactory(UpdateMapper updateMapper) {
this(updateMapper, updateMapper);
}
/**
* Create {@link StatementFactory} given {@link QueryMapper} and {@link UpdateMapper}.
*
* @param queryMapper must not be {@literal null}.
* @param updateMapper must not be {@literal null}.
*/
public StatementFactory(QueryMapper queryMapper, UpdateMapper updateMapper) {
Assert.notNull(queryMapper, "QueryMapper must not be null");
Assert.notNull(updateMapper, "UpdateMapper must not be null");
this.queryMapper = queryMapper;
this.updateMapper = updateMapper;
}
/**
* Returns the {@link QueryMapper} used to map {@link Query} to CQL-specific data types.
*
* @return the {@link QueryMapper} used to map {@link Query} to CQL-specific data types.
* @see QueryMapper
*/
protected QueryMapper getQueryMapper() {
return this.queryMapper;
}
/**
* Returns the {@link UpdateMapper} used to map {@link Update} to CQL-specific data types.
*
* @return the {@link UpdateMapper} used to map {@link Update} to CQL-specific data types.
* @see UpdateMapper
*/
protected UpdateMapper getUpdateMapper() {
return this.updateMapper;
}
/**
* Create a {@literal COUNT} statement by mapping {@link Query} to {@link Select}.
*
* @param query user-defined count {@link Query} to execute; must not be {@literal null}.
* @param entity {@link CassandraPersistentEntity entity} to count; must not be {@literal null}.
* @return the rendered {@link RegularStatement}.
* @since 2.1
*/
public RegularStatement count(Query query, CassandraPersistentEntity entity) {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entity, "Entity must not be null");
return count(query, entity, entity.getTableName());
}
/**
* Create a {@literal COUNT} statement by mapping {@link Query} to {@link Select}.
*
* @param query user-defined count {@link Query} to execute; must not be {@literal null}.
* @param entity {@link CassandraPersistentEntity entity} to count; must not be {@literal null}.
* @param tableName must not be {@literal null}.
* @return the rendered {@link RegularStatement}.
* @since 2.1
*/
public RegularStatement count(Query query, CassandraPersistentEntity entity, CqlIdentifier tableName) {
Filter filter = getQueryMapper().getMappedObject(query, entity);
List selectors = Collections.singletonList(FunctionCall.from("COUNT", 1L));
return createSelect(query, entity, filter, selectors, tableName);
}
/**
* Create a {@literal SELECT} statement by mapping {@link Query} to {@link Select}.
*
* @param query must not be {@literal null}.
* @param entity must not be {@literal null}.
* @return the rendered {@link RegularStatement}.
*/
public RegularStatement select(Query query, CassandraPersistentEntity entity) {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entity, "Entity must not be null");
return select(query, entity, entity.getTableName());
}
/**
* Create a {@literal SELECT} statement by mapping {@link Query} to {@link Select}.
*
* @param query must not be {@literal null}.
* @param entity must not be {@literal null}.
* @param tableName must not be {@literal null}.
* @return the rendered {@link RegularStatement}.
* @since 2.1
*/
public RegularStatement select(Query query, CassandraPersistentEntity entity, CqlIdentifier tableName) {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entity, "Entity must not be null");
Assert.notNull(entity, "Table name must not be null");
Filter filter = getQueryMapper().getMappedObject(query, entity);
List selectors = getQueryMapper().getMappedSelectors(query.getColumns(), entity);
return createSelect(query, entity, filter, selectors, tableName);
}
private Select createSelect(Query query, CassandraPersistentEntity entity, Filter filter, List selectors,
CqlIdentifier tableName) {
Sort sort = Optional.of(query.getSort()).map(querySort -> getQueryMapper().getMappedSort(querySort, entity))
.orElse(Sort.unsorted());
Select select = createSelectAndOrder(selectors, tableName, filter, sort);
query.getQueryOptions().ifPresent(queryOptions -> QueryOptionsUtil.addQueryOptions(select, queryOptions));
if (query.getLimit() > 0) {
select.limit(Ints.checkedCast(query.getLimit()));
}
if (query.isAllowFiltering()) {
select.allowFiltering();
}
query.getPagingState().ifPresent(select::setPagingState);
return select;
}
private static Select createSelectAndOrder(List selectors, CqlIdentifier from, Filter filter, Sort sort) {
Select select;
if (selectors.isEmpty()) {
select = QueryBuilder.select().all().from(from.toCql());
} else {
Selection selection = QueryBuilder.select();
selectors.forEach(
selector -> selector.getAlias().map(CqlIdentifier::toCql).ifPresent(getSelection(selection, selector)::as));
select = selection.from(from.toCql());
}
for (CriteriaDefinition criteriaDefinition : filter) {
select.where(toClause(criteriaDefinition));
}
if (sort.isSorted()) {
List orderings = new ArrayList<>();
for (Order order : sort) {
if (order.isAscending()) {
orderings.add(QueryBuilder.asc(order.getProperty()));
} else {
orderings.add(QueryBuilder.desc(order.getProperty()));
}
}
if (!orderings.isEmpty()) {
select.orderBy(orderings.toArray(new Ordering[orderings.size()]));
}
}
return select;
}
private static SelectionOrAlias getSelection(Selection selection, Selector selector) {
if (selector instanceof FunctionCall) {
Object[] objects = ((FunctionCall) selector).getParameters().stream().map(param -> {
if (param instanceof ColumnSelector) {
return QueryBuilder.column(((ColumnSelector) param).getExpression());
}
return param;
}).toArray();
return selection.fcall(selector.getExpression(), objects);
}
return selection.column(selector.getExpression());
}
/**
* Create an {@literal UPDATE} statement by mapping {@link Query} to {@link Update}.
*
* @param query must not be {@literal null}.
* @param entity must not be {@literal null}.
* @return the rendered {@link RegularStatement}.
*/
public RegularStatement update(Query query, Update update, CassandraPersistentEntity entity) {
Assert.notNull(query, "Query must not be null");
Assert.notNull(update, "Update must not be null");
Assert.notNull(entity, "Entity must not be null");
return update(query, update, entity, entity.getTableName());
}
/**
* Create an {@literal UPDATE} statement by mapping {@link Query} to {@link Update}.
*
* @param query must not be {@literal null}.
* @param updateObj must not be {@literal null}.
* @param entity must not be {@literal null}.
* @param tableName must not be {@literal null}.
* @return the rendered {@link RegularStatement}.
* @since 2.1
*/
RegularStatement update(Query query, Update updateObj, CassandraPersistentEntity entity, CqlIdentifier tableName) {
Assert.notNull(query, "Query must not be null");
Assert.notNull(updateObj, "Update must not be null");
Assert.notNull(entity, "Entity must not be null");
Assert.notNull(tableName, "Table name must not be null");
Filter filter = getQueryMapper().getMappedObject(query, entity);
Update mappedUpdate = getUpdateMapper().getMappedObject(updateObj, entity);
com.datastax.driver.core.querybuilder.Update update = update(tableName, mappedUpdate, filter);
query.getQueryOptions().ifPresent(queryOptions -> {
potentiallyApplyIfCondition(queryOptions, UpdateOptions.class, UpdateOptions::getIfCondition,
condition -> addIfCondition(condition, update, entity));
if (queryOptions instanceof WriteOptions) {
EntityQueryUtils.addWriteOptions(update, (WriteOptions) queryOptions);
} else {
QueryOptionsUtil.addQueryOptions(update, queryOptions);
}
});
query.getPagingState().ifPresent(update::setPagingState);
return update;
}
/**
* Create an {@literal UPDATE} statement by mapping {@code entity} to {@link Update} considering
* {@link UpdateOptions}.
*
* @param entity must not be {@literal null}.
* @param options must not be {@literal null}.
* @param entityWriter must not be {@literal null}.
* @param persistentEntity must not be {@literal null}.
* @param tableName must not be {@literal null}.
* @return the update object.
*/
com.datastax.driver.core.querybuilder.Update update(Object entity, WriteOptions options,
EntityWriter