DATACASS-512 - Support exists and count projections.

We now support exists and count projections derived from query methods and on Template API level.

Exists queries fetch rows limiting to a single row to optimize fetching.

Exists queries also support count results, i.e. a single row with a single numeric column is considered a count result. It's evaluated by comparing the number being greater to zero.

boolean exists = template.exists(Query.query(where("firstname").is("Walter")), Person.class);

long count = template.count(Query.query(where("lastname").is("White")), Person.class);

interface PersonRepository extends Repository<Person, String> {

    long countByLastname(String lastname); // derived query

    boolean existsByLastname(String lastname);  // derived query

    @CountQuery("SELECT COUNT(*) from users WHERE lastname = ?0")
    long countQueryByLastname(String lastname); // String-based query

    @ExistsQuery("SELECT * from users WHERE lastname = ?0 LIMIT 1")
    boolean existsQueryByLastname(String lastname);  // String-based query
}

Remove superfluous throws declarations.
This commit is contained in:
Mark Paluch
2018-01-19 10:16:00 +01:00
committed by John Blum
parent 4538b2aa56
commit c0b9479599
36 changed files with 1132 additions and 116 deletions

View File

@@ -230,7 +230,18 @@ public interface AsyncCassandraOperations {
ListenableFuture<Long> count(Class<?> entityClass) throws DataAccessException;
/**
* Determine whether the row {@code entityClass} with the given {@code id} exists.
* Returns the number of rows for the given entity class applying {@link Query}.
*
* @param query must not be {@literal null}.
* @param entityClass must not be {@literal null}.
* @return the number of existing entities.
* @throws DataAccessException if there is any problem executing the query.
* @since 2.1
*/
ListenableFuture<Long> count(Query query, Class<?> entityClass) throws DataAccessException;
/**
* Determine whether a row of {@code entityClass} with the given {@code id} exists.
*
* @param id the Id value. For single primary keys it's the plain value. For composite primary keys either the
* {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass} or
@@ -241,6 +252,17 @@ public interface AsyncCassandraOperations {
*/
ListenableFuture<Boolean> exists(Object id, Class<?> entityClass) throws DataAccessException;
/**
* Determine whether the result for {@code entityClass} {@link Query} yields at least one row.
*
* @param query must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return true, if the object exists.
* @throws DataAccessException if there is any problem executing the query.
* @since 2.1
*/
ListenableFuture<Boolean> exists(Query query, Class<?> entityClass) throws DataAccessException;
/**
* Execute the Select by {@code id} for the given {@code entityClass}.
*

View File

@@ -48,6 +48,7 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.driver.core.RegularStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.SimpleStatement;
@@ -405,6 +406,23 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations {
return getAsyncCqlOperations().queryForObject(select, Long.class);
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#count(org.springframework.data.cassandra.core.query.Query, java.lang.Class)
*/
@Override
public ListenableFuture<Long> count(Query query, Class<?> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
RegularStatement count = statementFactory.count(query,
getMappingContext().getRequiredPersistentEntity(entityClass));
ListenableFuture<Long> result = getAsyncCqlOperations().queryForObject(count, Long.class);
return new MappingListenableFutureAdapter<>(result, it -> it != null ? it : 0L);
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#exists(java.lang.Object, java.lang.Class)
*/
@@ -424,6 +442,22 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations {
resultSet -> resultSet.iterator().hasNext());
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#exists(org.springframework.data.cassandra.core.query.Query, java.lang.Class)
*/
@Override
public ListenableFuture<Boolean> exists(Query query, Class<?> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
RegularStatement select = statementFactory.select(query.limit(1),
getMappingContext().getRequiredPersistentEntity(entityClass));
return new MappingListenableFutureAdapter<>(getAsyncCqlOperations().queryForResultSet(select),
resultSet -> resultSet.iterator().hasNext());
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#selectOneById(java.lang.Object, java.lang.Class)
*/
@@ -537,8 +571,8 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations {
Assert.notNull(entityClass, "Entity type must not be null");
Truncate truncate = QueryBuilder.truncate(getMappingContext().getRequiredPersistentEntity(entityClass)
.getTableName().toCql());
Truncate truncate = QueryBuilder
.truncate(getMappingContext().getRequiredPersistentEntity(entityClass).getTableName().toCql());
return new MappingListenableFutureAdapter<>(getAsyncCqlOperations().execute(truncate), aBoolean -> null);
}
@@ -561,9 +595,8 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations {
}
}
return getAsyncCqlOperations().execute((AsyncSessionCallback<Integer>) session ->
AsyncResult.forValue(session.getCluster().getConfiguration().getQueryOptions().getFetchSize()))
.completable().join();
return getAsyncCqlOperations().execute((AsyncSessionCallback<Integer>) session -> AsyncResult
.forValue(session.getCluster().getConfiguration().getQueryOptions().getFetchSize())).completable().join();
}
static class MappingListenableFutureAdapter<T, S>

View File

@@ -257,7 +257,18 @@ public interface CassandraOperations {
long count(Class<?> entityClass) throws DataAccessException;
/**
* Determine whether the row {@code entityClass} with the given {@code id} exists.
* Returns the number of rows for the given entity class applying {@link Query}.
*
* @param query must not be {@literal null}.
* @param entityClass must not be {@literal null}.
* @return the number of existing entities.
* @throws DataAccessException if there is any problem executing the query.
* @since 2.1
*/
long count(Query query, Class<?> entityClass) throws DataAccessException;
/**
* Determine whether a row of {@code entityClass} with the given {@code id} exists.
*
* @param id the Id value. For single primary keys it's the plain value. For composite primary keys either the
* {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass} or
@@ -268,6 +279,17 @@ public interface CassandraOperations {
*/
boolean exists(Object id, Class<?> entityClass) throws DataAccessException;
/**
* Determine whether the result for {@code entityClass} {@link Query} yields at least one row.
*
* @param query must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return true, if the object exists.
* @throws DataAccessException if there is any problem executing the query.
* @since 2.1
*/
boolean exists(Query query, Class<?> entityClass) throws DataAccessException;
/**
* Execute the Select by {@code id} for the given {@code entityClass}.
*

View File

@@ -15,13 +15,13 @@
*/
package org.springframework.data.cassandra.core;
import lombok.NonNull;
import lombok.Value;
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import lombok.NonNull;
import lombok.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.SessionFactory;
import org.springframework.data.cassandra.core.convert.CassandraConverter;
@@ -45,6 +45,7 @@ import org.springframework.data.mapping.context.MappingContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import com.datastax.driver.core.RegularStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.SimpleStatement;
@@ -386,6 +387,23 @@ public class CassandraTemplate implements CassandraOperations {
return count != null ? count : 0L;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#count(org.springframework.data.cassandra.core.query.Query, java.lang.Class)
*/
@Override
public long count(Query query, Class<?> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
RegularStatement count = statementFactory.count(query,
getMappingContext().getRequiredPersistentEntity(entityClass));
Long result = getCqlOperations().queryForObject(count, Long.class);
return result != null ? result : 0L;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#exists(java.lang.Object, java.lang.Class)
*/
@@ -404,6 +422,21 @@ public class CassandraTemplate implements CassandraOperations {
return getCqlOperations().queryForResultSet(select).iterator().hasNext();
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#exists(org.springframework.data.cassandra.core.query.Query, java.lang.Class)
*/
@Override
public boolean exists(Query query, Class<?> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
RegularStatement select = statementFactory.select(query.limit(1),
getMappingContext().getRequiredPersistentEntity(entityClass));
return getCqlOperations().queryForResultSet(select).iterator().hasNext();
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#selectOneById(java.lang.Object, java.lang.Class)
*/
@@ -549,8 +582,8 @@ public class CassandraTemplate implements CassandraOperations {
}
}
return getCqlOperations().execute((SessionCallback<Integer>) session ->
session.getCluster().getConfiguration().getQueryOptions().getFetchSize());
return getCqlOperations().execute(
(SessionCallback<Integer>) session -> session.getCluster().getConfiguration().getQueryOptions().getFetchSize());
}
/* (non-Javadoc)

View File

@@ -165,7 +165,18 @@ public interface ReactiveCassandraOperations {
Mono<Long> count(Class<?> entityClass) throws DataAccessException;
/**
* Determine whether the row {@code entityClass} with the given {@code id} exists.
* Returns the number of rows for the given entity class applying {@link Query}.
*
* @param query must not be {@literal null}.
* @param entityClass must not be {@literal null}.
* @return the number of existing entities.
* @throws DataAccessException if there is any problem executing the query.
* @since 2.1
*/
Mono<Long> count(Query query, Class<?> entityClass) throws DataAccessException;
/**
* Determine whether a row of {@code entityClass} with the given {@code id} exists.
*
* @param id the Id value. For single primary keys it's the plain value. For composite primary keys either the
* {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass} or
@@ -176,6 +187,17 @@ public interface ReactiveCassandraOperations {
*/
Mono<Boolean> exists(Object id, Class<?> entityClass) throws DataAccessException;
/**
* Determine whether the result for {@code entityClass} {@link Query} yields at least one row.
*
* @param query must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return true, if the object exists.
* @throws DataAccessException if there is any problem executing the query.
* @since 2.1
*/
Mono<Boolean> exists(Query query, Class<?> entityClass) throws DataAccessException;
/**
* Execute the Select by {@code id} for the given {@code entityClass}.
*

View File

@@ -17,7 +17,6 @@ package org.springframework.data.cassandra.core;
import lombok.NonNull;
import lombok.Value;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -45,6 +44,7 @@ import org.springframework.data.mapping.context.MappingContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import com.datastax.driver.core.RegularStatement;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.SimpleStatement;
import com.datastax.driver.core.Statement;
@@ -265,8 +265,8 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
return select(getStatementFactory().select(query,
getMappingContext().getRequiredPersistentEntity(entityClass)), entityClass);
return select(getStatementFactory().select(query, getMappingContext().getRequiredPersistentEntity(entityClass)),
entityClass);
}
/* (non-Javadoc)
@@ -278,8 +278,8 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
return selectOne(getStatementFactory().select(query,
getMappingContext().getRequiredPersistentEntity(entityClass)), entityClass);
return selectOne(getStatementFactory().select(query, getMappingContext().getRequiredPersistentEntity(entityClass)),
entityClass);
}
/* (non-Javadoc)
@@ -293,8 +293,8 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations {
Assert.notNull(update, "Update must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
return getReactiveCqlOperations().execute(getStatementFactory().update(query, update,
getMappingContext().getRequiredPersistentEntity(entityClass)));
return getReactiveCqlOperations().execute(
getStatementFactory().update(query, update, getMappingContext().getRequiredPersistentEntity(entityClass)));
}
/* (non-Javadoc)
@@ -328,6 +328,21 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations {
return getReactiveCqlOperations().queryForObject(select, Long.class);
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#count(org.springframework.data.cassandra.core.query.Query, java.lang.Class)
*/
@Override
public Mono<Long> count(Query query, Class<?> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
RegularStatement count = statementFactory.count(query,
getMappingContext().getRequiredPersistentEntity(entityClass));
return getReactiveCqlOperations().queryForObject(count, Long.class);
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#exists(java.lang.Object, java.lang.Class)
*/
@@ -346,6 +361,21 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations {
return getReactiveCqlOperations().queryForRows(select).hasElements();
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#exists(org.springframework.data.cassandra.core.query.Query, java.lang.Class)
*/
@Override
public Mono<Boolean> exists(Query query, Class<?> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
RegularStatement select = statementFactory.select(query.limit(1),
getMappingContext().getRequiredPersistentEntity(entityClass));
return getReactiveCqlOperations().queryForRows(select).hasElements();
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#selectOneById(java.lang.Object, java.lang.Class)
*/

View File

@@ -16,6 +16,7 @@
package org.springframework.data.cassandra.core;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -140,10 +141,36 @@ public class StatementFactory {
List<Selector> selectors = getQueryMapper().getMappedSelectors(query.getColumns(), entity);
return createSelect(query, entity, filter, selectors);
}
/**
* Create a {@literal COUNT} 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}.
* @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");
Filter filter = getQueryMapper().getMappedObject(query, entity);
List<Selector> selectors = Collections.singletonList(FunctionCall.from("COUNT", 1L));
return createSelect(query, entity, filter, selectors);
}
private Select createSelect(Query query, CassandraPersistentEntity<?> entity, Filter filter,
List<Selector> selectors) {
Sort sort = Optional.of(query.getSort()).map(querySort -> getQueryMapper().getMappedSort(querySort, entity))
.orElse(Sort.unsorted());
Select select = select(selectors, entity.getTableName(), filter, sort);
Select select = createSelectAndOrder(selectors, entity.getTableName(), filter, sort);
query.getQueryOptions().ifPresent(queryOptions -> QueryOptionsUtil.addQueryOptions(select, queryOptions));
@@ -160,7 +187,7 @@ public class StatementFactory {
return select;
}
private static Select select(List<Selector> selectors, CqlIdentifier from, Filter filter, Sort sort) {
private static Select createSelectAndOrder(List<Selector> selectors, CqlIdentifier from, Filter filter, Sort sort) {
Select select;

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2018 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
*
* http://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.repository;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Annotation to declare count queries directly on repository methods. Both attributes allow using a placeholder
* notation of {@code ?0}, {@code ?1} and so on.
*
* @author Mark Paluch
* @since 2.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
@Query(count = true)
public @interface CountQuery {
/**
* A Cassandra CQL3 string to define the actual query to be executed. Placeholders {@code ?0}, {@code ?1}, etc are
* supported.
*/
@AliasFor(annotation = Query.class)
String value() default "";
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2018 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
*
* http://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.repository;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Annotation to declare exists queries directly on repository methods. Both attributes allow using a placeholder
* notation of {@code ?0}, {@code ?1} and so on.
*
* @author Mark Paluch
* @since 2.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
@Query(exists = true)
public @interface ExistsQuery {
/**
* A Cassandra CQL3 string to define the actual query to be executed. Placeholders {@code ?0}, {@code ?1}, etc are
* supported.
*/
@AliasFor(annotation = Query.class)
String value() default "";
}

View File

@@ -48,4 +48,18 @@ public @interface Query {
* @since 2.0
*/
boolean allowFiltering() default false;
/**
* Returns whether the query defined should be executed as count projection.
*
* @since 2.1
*/
boolean count() default false;
/**
* Returns whether the query defined should be executed as exists projection.
*
* @since 2.1
*/
boolean exists() default false;
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.cassandra.repository.query;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.CollectionExecution;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.ExistsExecution;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.ResultProcessingConverter;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.ResultProcessingExecution;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.ResultSetQuery;
@@ -52,7 +53,7 @@ public abstract class AbstractCassandraQuery extends CassandraRepositoryQuerySup
*/
public AbstractCassandraQuery(CassandraQueryMethod queryMethod, CassandraOperations operations) {
super(queryMethod);
super(queryMethod, operations.getConverter().getMappingContext());
Assert.notNull(operations, "CassandraOperations must not be null");
@@ -123,8 +124,28 @@ public abstract class AbstractCassandraQuery extends CassandraRepositoryQuerySup
return new ResultSetQuery(getOperations());
} else if (getQueryMethod().isStreamQuery()) {
return new StreamExecution(getOperations(), resultProcessing);
} else if (isCountQuery()) {
return ((statement, type) -> new SingleEntityExecution(getOperations()).execute(statement, Long.class));
} else if (isExistsQuery()) {
return new ExistsExecution(getOperations());
} else {
return new SingleEntityExecution(getOperations());
}
}
/**
* Returns whether the query should get a count projection applied.
*
* @return
* @since 2.1
*/
protected abstract boolean isCountQuery();
/**
* Returns whether the query should get an exists projection applied.
*
* @return
* @since 2.1
*/
protected abstract boolean isExistsQuery();
}

View File

@@ -18,10 +18,12 @@ package org.springframework.data.cassandra.repository.query;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.reactivestreams.Publisher;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryExecution.CollectionExecution;
import org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryExecution.ExistsExecution;
import org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryExecution.ResultProcessingConverter;
import org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryExecution.ResultProcessingExecution;
import org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryExecution.SingleEntityExecution;
@@ -30,8 +32,6 @@ import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.util.Assert;
import org.reactivestreams.Publisher;
import com.datastax.driver.core.Statement;
/**
@@ -54,7 +54,7 @@ public abstract class AbstractReactiveCassandraQuery extends CassandraRepository
*/
public AbstractReactiveCassandraQuery(ReactiveCassandraQueryMethod method, ReactiveCassandraOperations operations) {
super(method);
super(method, operations.getConverter().getMappingContext());
Assert.notNull(operations, "ReactiveCassandraOperations must not be null");
@@ -134,9 +134,33 @@ public abstract class AbstractReactiveCassandraQuery extends CassandraRepository
return new ResultProcessingExecution(getExecutionToWrap(), resultProcessing);
}
/* (non-Javadoc) */
private ReactiveCassandraQueryExecution getExecutionToWrap() {
return (getQueryMethod().isCollectionQuery() ? new CollectionExecution(getReactiveCassandraOperations())
: new SingleEntityExecution(getReactiveCassandraOperations()));
if (getQueryMethod().isCollectionQuery()) {
return new CollectionExecution(getReactiveCassandraOperations());
} else if (isCountQuery()) {
return ((statement, type) -> new SingleEntityExecution(getReactiveCassandraOperations()).execute(statement,
Long.class));
} else if (isExistsQuery()) {
return new ExistsExecution(getReactiveCassandraOperations());
} else {
return new SingleEntityExecution(getReactiveCassandraOperations());
}
}
/**
* Returns whether the query should get a count projection applied.
*
* @return
* @since 2.1
*/
protected abstract boolean isCountQuery();
/**
* Returns whether the query should get an exists projection applied.
*
* @return
* @since 2.1
*/
protected abstract boolean isExistsQuery();
}

View File

@@ -18,6 +18,8 @@ package org.springframework.data.cassandra.repository.query;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.Iterator;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
@@ -31,6 +33,8 @@ import org.springframework.data.repository.query.ReturnedType;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Statement;
/**
@@ -132,6 +136,44 @@ interface CassandraQueryExecution {
}
}
/**
* {@link CassandraQueryExecution} for an Exists query supporting count and regular row-data for exists calculation.
*
* @author Mark Paluch
* @since 2.1
*/
@RequiredArgsConstructor
final class ExistsExecution implements CassandraQueryExecution {
private final @NonNull CassandraOperations operations;
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.CassandraQueryExecution#execute(java.lang.String, java.lang.Class)
*/
@Override
public Object execute(Statement statement, Class<?> type) {
ResultSet resultSet = operations.getCqlOperations().queryForResultSet(statement);
Iterator<Row> iterator = resultSet.iterator();
if (iterator.hasNext()) {
Row row = iterator.next();
if (!iterator.hasNext() && ProjectionUtil.isCountProjection(row)) {
Object object = row.getObject(0);
return ((Number) object).longValue() > 0;
}
return true;
}
return false;
}
}
/**
* {@link CassandraQueryExecution} to return a {@link com.datastax.driver.core.ResultSet}.
*

View File

@@ -15,21 +15,24 @@
*/
package org.springframework.data.cassandra.repository.query;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for Cassandra {@link RepositoryQuery} implementations providing common infrastructure such as
* {@link EntityInstantiators} and {@link QueryStatementCreator}.
@@ -53,15 +56,35 @@ public abstract class CassandraRepositoryQuerySupport implements RepositoryQuery
* {@link CassandraOperations}.
*
* @param queryMethod must not be {@literal null}.
* @param operations must not be {@literal null}.
* @deprecated use {@link #CassandraRepositoryQuerySupport(CassandraQueryMethod, MappingContext)}
*/
@Deprecated
public CassandraRepositoryQuerySupport(CassandraQueryMethod queryMethod) {
Assert.notNull(queryMethod, "CassandraQueryMethod must not be null");
this.queryMethod = queryMethod;
this.instantiators = new EntityInstantiators();
this.queryStatementCreator = new QueryStatementCreator(queryMethod);
this.queryStatementCreator = new QueryStatementCreator(queryMethod, new CassandraMappingContext());
}
/**
* Create a new {@link AbstractCassandraQuery} from the given {@link CassandraQueryMethod} and
* {@link CassandraOperations}.
*
* @param queryMethod must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
* @since 2.1
*/
public CassandraRepositoryQuerySupport(CassandraQueryMethod queryMethod,
MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext) {
Assert.notNull(queryMethod, "CassandraQueryMethod must not be null");
Assert.notNull(mappingContext, "CassandraMappingContext must not be null");
this.queryMethod = queryMethod;
this.instantiators = new EntityInstantiators();
this.queryStatementCreator = new QueryStatementCreator(queryMethod, mappingContext);
}
/* (non-Javadoc)

View File

@@ -95,7 +95,31 @@ public class PartTreeCassandraQuery extends AbstractCassandraQuery {
*/
@Override
protected Statement createQuery(CassandraParameterAccessor parameterAccessor) {
return getQueryStatementCreator().select(getStatementFactory(), getTree(), getMappingContext(),
parameterAccessor);
if (isCountQuery()) {
return getQueryStatementCreator().count(getStatementFactory(), getTree(), parameterAccessor);
}
if (isExistsQuery()) {
return getQueryStatementCreator().exists(getStatementFactory(), getTree(), parameterAccessor);
}
return getQueryStatementCreator().select(getStatementFactory(), getTree(), parameterAccessor);
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.AbstractCassandraQuery#isCountQuery()
*/
@Override
protected boolean isCountQuery() {
return tree.isCountProjection();
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.AbstractCassandraQuery#isExistsQuery()
*/
@Override
protected boolean isExistsQuery() {
return tree.isExistsProjection();
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2018 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
*
* http://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.repository.query;
import lombok.experimental.UtilityClass;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.datastax.driver.core.ColumnDefinitions;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.Row;
/**
* Utility methods for projections.
*
* @author Mark Paluch
* @since 2.1
*/
@UtilityClass
class ProjectionUtil {
private final static Set<DataType> NUMERIC_TYPES = new HashSet<>(Arrays.asList(DataType.bigint(), DataType.varint(),
DataType.smallint(), DataType.cint(), DataType.counter(), DataType.tinyint()));
/**
* Determine wether the {@link Row} qualifies for a count projection. Count projection candidates have a single
* numeric column.
*
* @param row
* @return
*/
static boolean isCountProjection(Row row) {
ColumnDefinitions columnDefinitions = row.getColumnDefinitions();
return columnDefinitions.size() == 1 && NUMERIC_TYPES.contains(columnDefinitions.getType(0));
}
/**
* Determine whether multiple {@code boolean} flags are set. Allowed is at most a single {@literal true} value.
*
* @param flags
* @return {@literal true} if more than one {@code flag} is set to {@literal true}.
*/
static boolean hasAmbiguousProjectionFlags(Boolean... flags) {
return Arrays.stream(flags).filter(Boolean::booleanValue).count() > 1;
}
}

View File

@@ -15,10 +15,13 @@
*/
package org.springframework.data.cassandra.repository.query;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import java.util.Optional;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.cassandra.core.StatementFactory;
import org.springframework.data.cassandra.core.cql.QueryOptions;
import org.springframework.data.cassandra.core.cql.QueryOptionsUtil;
@@ -29,9 +32,6 @@ import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.query.QueryCreationException;
import org.springframework.data.repository.query.parser.PartTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.datastax.driver.core.RegularStatement;
import com.datastax.driver.core.SimpleStatement;
import com.datastax.driver.core.Statement;
@@ -50,19 +50,104 @@ class QueryStatementCreator {
private final CassandraQueryMethod queryMethod;
private final MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext;
/**
* Create a {@literal SELECT} {@link Statement} from a {@link PartTree} and apply query options.
*
* @param statementFactory must not be {@literal null}.
* @param tree must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
* @param parameterAccessor must not be {@literal null}.
* @return the {@literal SELECT} {@link Statement}.
*/
Statement select(StatementFactory statementFactory, PartTree tree,
MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext,
CassandraParameterAccessor parameterAccessor) {
Function<Query, Statement> function = query -> {
CassandraPersistentEntity<?> persistentEntity = mappingContext
.getRequiredPersistentEntity(queryMethod.getDomainClass());
RegularStatement statement = statementFactory.select(query, persistentEntity);
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Created query [%s].", statement));
}
return statement;
};
return doWithQuery(parameterAccessor, tree, function);
}
/**
* Create a {@literal COUNT} {@link Statement} from a {@link PartTree} and apply query options.
*
* @param statementFactory must not be {@literal null}.
* @param tree must not be {@literal null}.
* @param parameterAccessor must not be {@literal null}.
* @return the {@literal SELECT} {@link Statement}.
* @since 2.1
*/
Statement count(StatementFactory statementFactory, PartTree tree, CassandraParameterAccessor parameterAccessor) {
Function<Query, Statement> function = query -> {
CassandraPersistentEntity<?> persistentEntity = mappingContext
.getRequiredPersistentEntity(queryMethod.getDomainClass());
RegularStatement statement = statementFactory.count(query, persistentEntity);
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Created query [%s].", statement));
}
return statement;
};
return doWithQuery(parameterAccessor, tree, function);
}
/**
* Create a {@literal SELECT} {@link Statement} from a {@link PartTree} and apply query options for exists query
* execution. Limit results to a single row.
*
* @param statementFactory must not be {@literal null}.
* @param tree must not be {@literal null}.
* @param parameterAccessor must not be {@literal null}.
* @return the {@literal SELECT} {@link Statement}.
* @since 2.1
*/
Statement exists(StatementFactory statementFactory, PartTree tree, CassandraParameterAccessor parameterAccessor) {
Function<Query, Statement> function = query -> {
CassandraPersistentEntity<?> persistentEntity = mappingContext
.getRequiredPersistentEntity(queryMethod.getDomainClass());
RegularStatement statement = statementFactory.select(query.limit(1), persistentEntity);
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Created query [%s].", statement));
}
return statement;
};
return doWithQuery(parameterAccessor, tree, function);
}
/**
* A {@link Function} to {@link Query} derived from a {@link PartTree} and apply query options.
*
* @param parameterAccessor must not be {@literal null}.
* @param tree must not be {@literal null}.
* @param function callback function must not be {@literal null}.
* @return the {@literal SELECT} {@link Statement}.
*/
<T> T doWithQuery(CassandraParameterAccessor parameterAccessor, PartTree tree,
Function<Query, ? extends T> function) {
CassandraQueryCreator queryCreator = new CassandraQueryCreator(tree, parameterAccessor, mappingContext);
Query query = queryCreator.createQuery();
@@ -89,17 +174,7 @@ class QueryStatementCreator {
.build());
}
CassandraPersistentEntity<?> persistentEntity =
mappingContext.getRequiredPersistentEntity(queryMethod.getDomainClass());
RegularStatement statement = statementFactory.select(query, persistentEntity);
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Created query [%s].", statement));
}
return statement;
return function.apply(query);
} catch (RuntimeException e) {
throw QueryCreationException.create(queryMethod, e);
}

View File

@@ -17,6 +17,9 @@ package org.springframework.data.cassandra.repository.query;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Mono;
import java.util.List;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
@@ -28,6 +31,7 @@ import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.util.ClassUtils;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Statement;
/**
@@ -81,6 +85,48 @@ interface ReactiveCassandraQueryExecution {
}
}
/**
* {@link ReactiveCassandraQueryExecution} for an Exists query supporting count and regular row-data for exists
* calculation.
*
* @author Mark Paluch
* @since 2.1
*/
@RequiredArgsConstructor
final class ExistsExecution implements ReactiveCassandraQueryExecution {
private final @NonNull ReactiveCassandraOperations operations;
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryExecution#execute(com.datastax.driver.core.Statement, java.lang.Class)
*/
@Override
public Object execute(Statement statement, Class<?> type) {
Mono<List<Row>> rows = operations.getReactiveCqlOperations().queryForRows(statement).buffer(2).next();
return rows.map(it -> {
if (it.isEmpty()) {
return false;
}
if (it.size() == 1) {
Row row = it.get(0);
if (ProjectionUtil.isCountProjection(row)) {
Object object = row.getObject(0);
return ((Number) object).longValue() > 0;
}
}
return true;
}).switchIfEmpty(Mono.just(false));
}
}
/**
* An {@link ReactiveCassandraQueryExecution} that wraps the results of the given delegate with the given result
* processing.

View File

@@ -95,6 +95,31 @@ public class ReactivePartTreeCassandraQuery extends AbstractReactiveCassandraQue
*/
@Override
protected Statement createQuery(CassandraParameterAccessor parameterAccessor) {
return getQueryStatementCreator().select(getStatementFactory(), getTree(), getMappingContext(), parameterAccessor);
if (isCountQuery()) {
return getQueryStatementCreator().count(getStatementFactory(), getTree(), parameterAccessor);
}
if (isExistsQuery()) {
return getQueryStatementCreator().exists(getStatementFactory(), getTree(), parameterAccessor);
}
return getQueryStatementCreator().select(getStatementFactory(), getTree(), parameterAccessor);
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.AbstractReactiveCassandraQuery#isCountQuery()
*/
@Override
protected boolean isCountQuery() {
return tree.isCountProjection();
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.AbstractReactiveCassandraQuery#isExistsQuery()
*/
@Override
protected boolean isExistsQuery() {
return tree.isExistsProjection();
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.cassandra.repository.query;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
@@ -36,8 +37,14 @@ import com.datastax.driver.core.SimpleStatement;
*/
public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandraQuery {
private static final String COUNT_AND_EXISTS = "Manually defined query for %s cannot be a count and exists query at the same time!";
private final StringBasedQuery stringBasedQuery;
private final boolean isCountQuery;
private final boolean isExistsQuery;
/**
* Create a new {@link ReactiveStringBasedCassandraQuery} for the given {@link CassandraQueryMethod},
* {@link ReactiveCassandraOperations}, {@link SpelExpressionParser}, and {@link EvaluationContextProvider}.
@@ -59,22 +66,38 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
* Create a new {@link ReactiveStringBasedCassandraQuery} for the given {@code query}, {@link CassandraQueryMethod},
* {@link ReactiveCassandraOperations}, {@link SpelExpressionParser}, and {@link EvaluationContextProvider}.
*
* @param queryMethod {@link ReactiveCassandraQueryMethod} on which this query is based.
* @param method {@link ReactiveCassandraQueryMethod} on which this query is based.
* @param operations {@link ReactiveCassandraOperations} used to perform data access in Cassandra.
* @param expressionParser {@link SpelExpressionParser} used to parse expressions in the query.
* @param evaluationContextProvider {@link EvaluationContextProvider} used to access the potentially shared
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}.
*/
public ReactiveStringBasedCassandraQuery(String query, ReactiveCassandraQueryMethod queryMethod,
public ReactiveStringBasedCassandraQuery(String query, ReactiveCassandraQueryMethod method,
ReactiveCassandraOperations operations, SpelExpressionParser expressionParser,
EvaluationContextProvider evaluationContextProvider) {
super(queryMethod, operations);
super(method, operations);
Assert.hasText(query, "Query must not be empty");
this.stringBasedQuery = new StringBasedQuery(query,
new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider));
if (method.hasAnnotatedQuery()) {
Query queryAnnotation = method.getQueryAnnotation().get();
this.isCountQuery = queryAnnotation.count();
this.isExistsQuery = queryAnnotation.exists();
if (ProjectionUtil.hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery)) {
throw new IllegalArgumentException(String.format(COUNT_AND_EXISTS, method));
}
} else {
this.isCountQuery = false;
this.isExistsQuery = false;
}
}
protected StringBasedQuery getStringBasedQuery() {
@@ -88,4 +111,20 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
public SimpleStatement createQuery(CassandraParameterAccessor parameterAccessor) {
return getQueryStatementCreator().select(getStringBasedQuery(), parameterAccessor);
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.AbstractReactiveCassandraQuery#isCountQuery()
*/
@Override
protected boolean isCountQuery() {
return isCountQuery;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.AbstractReactiveCassandraQuery#isExistsQuery()
*/
@Override
protected boolean isExistsQuery() {
return isExistsQuery;
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.cassandra.repository.query;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -35,8 +36,14 @@ import com.datastax.driver.core.SimpleStatement;
*/
public class StringBasedCassandraQuery extends AbstractCassandraQuery {
private static final String COUNT_AND_EXISTS = "Manually defined query for %s cannot be a count and exists query at the same time!";
private final StringBasedQuery stringBasedQuery;
private final boolean isCountQuery;
private final boolean isExistsQuery;
/**
* Create a new {@link StringBasedCassandraQuery} for the given {@link CassandraQueryMethod},
* {@link CassandraOperations}, {@link SpelExpressionParser}, and {@link EvaluationContextProvider}.
@@ -58,19 +65,35 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
* {@link CassandraOperations}, {@link SpelExpressionParser}, and {@link EvaluationContextProvider}.
*
* @param query
* @param queryMethod {@link CassandraQueryMethod} on which this query is based.
* @param method {@link CassandraQueryMethod} on which this query is based.
* @param operations {@link CassandraOperations} used to perform data access in Cassandra.
* @param expressionParser {@link SpelExpressionParser} used to parse expressions in the query.
* @param evaluationContextProvider {@link EvaluationContextProvider} used to access the potentially shared
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}.
*/
public StringBasedCassandraQuery(String query, CassandraQueryMethod queryMethod, CassandraOperations operations,
public StringBasedCassandraQuery(String query, CassandraQueryMethod method, CassandraOperations operations,
SpelExpressionParser expressionParser, EvaluationContextProvider evaluationContextProvider) {
super(queryMethod, operations);
super(method, operations);
this.stringBasedQuery = new StringBasedQuery(query,
new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider));
if (method.hasAnnotatedQuery()) {
Query queryAnnotation = method.getQueryAnnotation().get();
this.isCountQuery = queryAnnotation.count();
this.isExistsQuery = queryAnnotation.exists();
if (ProjectionUtil.hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery)) {
throw new IllegalArgumentException(String.format(COUNT_AND_EXISTS, method));
}
} else {
this.isCountQuery = false;
this.isExistsQuery = false;
}
}
protected StringBasedQuery getStringBasedQuery() {
@@ -84,4 +107,20 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
public SimpleStatement createQuery(CassandraParameterAccessor parameterAccessor) {
return getQueryStatementCreator().select(getStringBasedQuery(), parameterAccessor);
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.AbstractCassandraQuery#isCountQuery()
*/
@Override
protected boolean isCountQuery() {
return isCountQuery;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.AbstractCassandraQuery#isExistsQuery()
*/
@Override
protected boolean isExistsQuery() {
return isExistsQuery;
}
}

View File

@@ -15,7 +15,8 @@
*/
package org.springframework.data.cassandra.core;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.cassandra.core.query.Criteria.*;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -24,12 +25,10 @@ import java.util.concurrent.Future;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.cql.AsyncCqlTemplate;
import org.springframework.data.cassandra.core.query.CassandraPageRequest;
import org.springframework.data.cassandra.core.query.Columns;
import org.springframework.data.cassandra.core.query.Criteria;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.core.query.Update;
import org.springframework.data.cassandra.domain.User;
@@ -52,7 +51,7 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
private AsyncCassandraTemplate template;
@Before
public void setUp() throws Exception {
public void setUp() {
MappingCassandraConverter converter = new MappingCassandraConverter();
CassandraTemplate cassandraTemplate = new CassandraTemplate(session, converter);
@@ -80,7 +79,7 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
getUninterruptibly(template.insert(token1));
getUninterruptibly(template.insert(token2));
Query query = Query.query(Criteria.where("userId").is(token1.getUserId())).sort(Sort.by("token"));
Query query = Query.query(where("userId").is(token1.getUserId())).sort(Sort.by("token"));
assertThat(getUninterruptibly(template.select(query, UserToken.class))).containsSequence(token1, token2);
}
@@ -95,7 +94,7 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
getUninterruptibly(template.insert(token1));
Query query = Query.query(Criteria.where("userId").is(token1.getUserId()));
Query query = Query.query(where("userId").is(token1.getUserId()));
assertThat(getUninterruptibly(template.selectOne(query, UserToken.class))).isEqualTo(token1);
}
@@ -143,7 +142,7 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
}
@Test // DATACASS-292
public void shouldInsertAndCountEntities() throws Exception {
public void shouldInsertAndCountEntities() {
User user = new User("heisenberg", "Walter", "White");
@@ -153,8 +152,30 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
assertThat(getUninterruptibly(count)).isEqualTo(1L);
}
@Test // DATACASS-512
public void shouldInsertEntityAndCountByQuery() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
assertThat(getUninterruptibly(template.count(Query.query(where("id").is("heisenberg")), User.class))).isOne();
assertThat(getUninterruptibly(template.count(Query.query(where("id").is("foo")), User.class))).isZero();
}
@Test // DATACASS-512
public void shouldInsertEntityAndExistsByQuery() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
assertThat(getUninterruptibly(template.exists(Query.query(where("id").is("heisenberg")), User.class))).isTrue();
assertThat(getUninterruptibly(template.exists(Query.query(where("id").is("foo")), User.class))).isFalse();
}
@Test // DATACASS-292
public void updateShouldUpdateEntity() throws Exception {
public void updateShouldUpdateEntity() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
@@ -197,12 +218,12 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
}
@Test // DATACASS-343
public void updateShouldUpdateEntityByQuery() throws Exception {
public void updateShouldUpdateEntityByQuery() {
User user = new User("heisenberg", "Walter", "White");
template.insert(user).get();
getUninterruptibly(template.insert(user));
Query query = Query.query(Criteria.where("id").is("heisenberg"));
Query query = Query.query(where("id").is("heisenberg"));
boolean result = getUninterruptibly(
template.update(query, Update.empty().set("firstname", "Walter Hartwell"), User.class));
assertThat(result).isTrue();
@@ -211,24 +232,24 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
}
@Test // DATACASS-343
public void deleteByQueryShouldRemoveEntity() throws Exception {
public void deleteByQueryShouldRemoveEntity() {
User user = new User("heisenberg", "Walter", "White");
template.insert(user).get();
getUninterruptibly(template.insert(user));
Query query = Query.query(Criteria.where("id").is("heisenberg"));
Query query = Query.query(where("id").is("heisenberg"));
assertThat(getUninterruptibly(template.delete(query, User.class))).isTrue();
assertThat(getUser(user.getId())).isNull();
}
@Test // DATACASS-343
public void deleteColumnsByQueryShouldRemoveColumn() throws Exception {
public void deleteColumnsByQueryShouldRemoveColumn() {
User user = new User("heisenberg", "Walter", "White");
template.insert(user).get();
getUninterruptibly(template.insert(user));
Query query = Query.query(Criteria.where("id").is("heisenberg")).columns(Columns.from("lastname"));
Query query = Query.query(where("id").is("heisenberg")).columns(Columns.from("lastname"));
assertThat(getUninterruptibly(template.delete(query, User.class))).isTrue();
@@ -238,7 +259,7 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
}
@Test // DATACASS-292
public void deleteShouldRemoveEntity() throws Exception {
public void deleteShouldRemoveEntity() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
@@ -250,7 +271,7 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
}
@Test // DATACASS-292
public void deleteByIdShouldRemoveEntity() throws Exception {
public void deleteByIdShouldRemoveEntity() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));

View File

@@ -37,6 +37,7 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.cassandra.CassandraConnectionFailureException;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.domain.User;
import org.springframework.util.concurrent.ListenableFuture;
@@ -208,6 +209,18 @@ public class AsyncCassandraTemplateUnitTests {
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM users WHERE id='myid';");
}
@Test // DATACASS-512
public void existsByQueryShouldReturnExistingElement() {
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
ListenableFuture<Boolean> future = template.exists(Query.empty(), User.class);
assertThat(getUninterruptibly(future)).isTrue();
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM users LIMIT 1;");
}
@Test // DATACASS-292
public void countShouldExecuteCountQueryElement() {
@@ -222,6 +235,20 @@ public class AsyncCassandraTemplateUnitTests {
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT count(*) FROM users;");
}
@Test // DATACASS-292
public void countByQueryShouldExecuteCountQueryElement() {
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
when(row.getLong(0)).thenReturn(42L);
when(columnDefinitions.size()).thenReturn(1);
ListenableFuture<Long> future = template.count(Query.empty(), User.class);
assertThat(getUninterruptibly(future)).isEqualTo(42L);
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT COUNT(1) FROM users;");
}
@Test // DATACASS-292
public void insertShouldInsertEntity() {

View File

@@ -15,8 +15,9 @@
*/
package org.springframework.data.cassandra.core;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assume.assumeTrue;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import static org.springframework.data.cassandra.core.query.Criteria.*;
import java.util.Arrays;
import java.util.Collections;
@@ -29,13 +30,11 @@ import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.cql.CqlTemplate;
import org.springframework.data.cassandra.core.mapping.BasicMapId;
import org.springframework.data.cassandra.core.query.CassandraPageRequest;
import org.springframework.data.cassandra.core.query.Columns;
import org.springframework.data.cassandra.core.query.Criteria;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.core.query.Update;
import org.springframework.data.cassandra.domain.BookReference;
@@ -93,8 +92,8 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
template.insert(userToken);
Query query = Query.query(Criteria.where("userId").is(userToken.getUserId()))
.and(Criteria.where("userComment").is("cook")).withAllowFiltering();
Query query = Query.query(where("userId").is(userToken.getUserId())).and(where("userComment").is("cook"))
.withAllowFiltering();
UserToken loaded = template.selectOne(query, UserToken.class);
assertThat(loaded).isNotNull();
@@ -117,7 +116,7 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
template.insert(token1);
template.insert(token2);
Query query = Query.query(Criteria.where("userId").is(token1.getUserId())).sort(Sort.by("token"));
Query query = Query.query(where("userId").is(token1.getUserId())).sort(Sort.by("token"));
List<UserToken> loaded = template.select(query, UserToken.class);
assertThat(loaded).containsSequence(token1, token2);
@@ -133,7 +132,7 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
template.insert(token1);
Query query = Query.query(Criteria.where("userId").is(token1.getUserId()));
Query query = Query.query(where("userId").is(token1.getUserId()));
UserToken loaded = template.selectOne(query, UserToken.class);
assertThat(loaded).isEqualTo(token1);
@@ -191,6 +190,28 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
assertThat(count).isEqualTo(1L);
}
@Test // DATACASS-512
public void shouldInsertEntityAndCountByQuery() {
User user = new User("heisenberg", "Walter", "White");
template.insert(user);
assertThat(template.count(Query.query(where("id").is("heisenberg")), User.class)).isOne();
assertThat(template.count(Query.query(where("id").is("foo")), User.class)).isZero();
}
@Test // DATACASS-512
public void shouldInsertEntityAndExistsByQuery() {
User user = new User("heisenberg", "Walter", "White");
template.insert(user);
assertThat(template.exists(Query.query(where("id").is("heisenberg")), User.class)).isTrue();
assertThat(template.exists(Query.query(where("id").is("foo")), User.class)).isFalse();
}
@Test // DATACASS-292
public void updateShouldUpdateEntity() {
@@ -240,7 +261,7 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
User person = new User("heisenberg", "Walter", "White");
template.insert(person);
Query query = Query.query(Criteria.where("id").is("heisenberg"));
Query query = Query.query(where("id").is("heisenberg"));
boolean result = template.update(query, Update.empty().set("firstname", "Walter Hartwell"), User.class);
assertThat(result).isTrue();
@@ -253,7 +274,7 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
User user = new User("heisenberg", "Walter", "White");
template.insert(user);
Query query = Query.query(Criteria.where("id").is("heisenberg"));
Query query = Query.query(where("id").is("heisenberg"));
assertThat(template.delete(query, User.class)).isTrue();
assertThat(template.selectOneById(user.getId(), User.class)).isNull();
@@ -265,7 +286,7 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
User user = new User("heisenberg", "Walter", "White");
template.insert(user);
Query query = Query.query(Criteria.where("id").is("heisenberg")).columns(Columns.from("lastname"));
Query query = Query.query(where("id").is("heisenberg")).columns(Columns.from("lastname"));
assertThat(template.delete(query, User.class)).isTrue();
@@ -314,7 +335,7 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
User person = new User("heisenberg", "Walter", "White");
template.insert(person);
Query query = Query.query(Criteria.where("id").is("heisenberg"));
Query query = Query.query(where("id").is("heisenberg"));
Stream<User> stream = template.stream(query, User.class);

View File

@@ -34,6 +34,7 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.cassandra.CassandraConnectionFailureException;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.domain.User;
import com.datastax.driver.core.ColumnDefinitions;
@@ -175,6 +176,18 @@ public class CassandraTemplateUnitTests {
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM users WHERE id='myid';");
}
@Test // DATACASS-512
public void existsByQueryShouldReturnExistingElement() {
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
boolean exists = template.exists(Query.empty(), User.class);
assertThat(exists).isTrue();
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM users LIMIT 1;");
}
@Test // DATACASS-292
public void countShouldExecuteCountQueryElement() {
@@ -189,6 +202,20 @@ public class CassandraTemplateUnitTests {
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT count(*) FROM users;");
}
@Test // DATACASS-512
public void countByQueryShouldExecuteCountQueryElement() {
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
when(row.getLong(0)).thenReturn(42L);
when(columnDefinitions.size()).thenReturn(1);
long count = template.count(Query.empty(), User.class);
assertThat(count).isEqualTo(42L);
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT COUNT(1) FROM users;");
}
@Test // DATACASS-292
public void insertShouldInsertEntity() {

View File

@@ -16,6 +16,7 @@
package org.springframework.data.cassandra.core;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.cassandra.core.query.Criteria.*;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
@@ -49,7 +50,7 @@ public class ReactiveCassandraTemplateIntegrationTests extends AbstractKeyspaceC
ReactiveCassandraTemplate template;
@Before
public void setUp() throws Exception {
public void setUp() {
MappingCassandraConverter converter = new MappingCassandraConverter();
CassandraTemplate cassandraTemplate = new CassandraTemplate(this.session, converter);
@@ -107,7 +108,7 @@ public class ReactiveCassandraTemplateIntegrationTests extends AbstractKeyspaceC
}
@Test // DATACASS-335
public void shouldInsertAndCountEntities() {
public void shouldInsertEntityAndCount() {
User user = new User("heisenberg", "Walter", "White");
@@ -116,6 +117,38 @@ public class ReactiveCassandraTemplateIntegrationTests extends AbstractKeyspaceC
StepVerifier.create(template.count(User.class)).expectNext(1L).verifyComplete();
}
@Test // DATACASS-335
public void shouldInsertEntityAndCountByQuery() {
User user = new User("heisenberg", "Walter", "White");
StepVerifier.create(template.insert(user)).expectNextCount(1).verifyComplete();
StepVerifier.create(template.count(Query.query(where("id").is("heisenberg")), User.class)) //
.expectNext(1L) //
.verifyComplete();
StepVerifier.create(template.count(Query.query(where("id").is("foo")), User.class)) //
.expectNext(0L) //
.verifyComplete();
}
@Test // DATACASS-335
public void shouldInsertAndExistsByQueryEntities() {
User user = new User("heisenberg", "Walter", "White");
StepVerifier.create(template.insert(user)).expectNextCount(1).verifyComplete();
StepVerifier.create(template.exists(Query.query(where("id").is("heisenberg")), User.class)) //
.expectNext(true) //
.verifyComplete();
StepVerifier.create(template.exists(Query.query(where("id").is("foo")), User.class)) //
.expectNext(false) //
.verifyComplete();
}
@Test // DATACASS-335
public void updateShouldUpdateEntity() {

View File

@@ -36,6 +36,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.cassandra.ReactiveResultSet;
import org.springframework.data.cassandra.ReactiveSession;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.domain.User;
import com.datastax.driver.core.ColumnDefinitions;
@@ -149,6 +150,28 @@ public class ReactiveCassandraTemplateUnitTests {
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM users WHERE id='myid';");
}
@Test // DATACASS-512
public void existsByQueryShouldReturnExistingElement() {
when(reactiveResultSet.rows()).thenReturn(Flux.just(row));
StepVerifier.create(template.exists(Query.empty(), User.class)).expectNext(true).verifyComplete();
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM users LIMIT 1;");
}
@Test // DATACASS-512
public void existsByQueryShouldReturnNonExistingElement() {
when(reactiveResultSet.rows()).thenReturn(Flux.empty());
StepVerifier.create(template.exists(Query.empty(), User.class)).expectNext(false).verifyComplete();
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM users LIMIT 1;");
}
@Test // DATACASS-335
public void countShouldExecuteCountQueryElement() {
@@ -162,6 +185,19 @@ public class ReactiveCassandraTemplateUnitTests {
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT count(*) FROM users;");
}
@Test // DATACASS-512
public void countByQueryShouldExecuteCountQueryElement() {
when(reactiveResultSet.rows()).thenReturn(Flux.just(row));
when(row.getLong(0)).thenReturn(42L);
when(columnDefinitions.size()).thenReturn(1);
StepVerifier.create(template.count(Query.empty(), User.class)).expectNext(42L).verifyComplete();
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT COUNT(1) FROM users;");
}
@Test // DATACASS-335
public void insertShouldInsertEntity() {

View File

@@ -229,6 +229,17 @@ public class StatementFactoryUnitTests {
assertThat(update.toString()).isEqualTo("UPDATE person SET number=number-1;");
}
@Test // DATACASS-512
public void shouldCreateCountQuery() {
Query query = Query.query(Criteria.where("foo").is("bar"));
Statement count = statementFactory.count(query,
converter.getMappingContext().getRequiredPersistentEntity(Group.class));
assertThat(count.toString()).isEqualTo("SELECT COUNT(1) FROM group WHERE foo='bar';");
}
static class Person {
@Id String id;

View File

@@ -15,8 +15,8 @@
*/
package org.springframework.data.cassandra.repository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assume.assumeTrue;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import java.time.LocalDate;
import java.util.ArrayList;
@@ -26,10 +26,10 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
@@ -55,8 +55,6 @@ import org.springframework.data.util.Version;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.assertj.core.api.Assertions;
import com.datastax.driver.core.Session;
/**
@@ -183,7 +181,7 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
Collection<PersonProjection> collection = personRepository.findPersonProjectedBy();
Assertions.assertThat(collection).hasSize(3).extracting("firstname").contains(flynn.getFirstname(),
assertThat(collection).hasSize(3).extracting("firstname").contains(flynn.getFirstname(),
skyler.getFirstname(), walter.getFirstname());
}
@@ -192,7 +190,7 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
Collection<PersonDto> collection = personRepository.findPersonDtoBy();
Assertions.assertThat(collection).hasSize(3).extracting("firstname").contains(flynn.getFirstname(),
assertThat(collection).hasSize(3).extracting("firstname").contains(flynn.getFirstname(),
skyler.getFirstname(), walter.getFirstname());
}
@@ -320,6 +318,21 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
assertThat(result).contains(walter, skyler, flynn);
}
@Test // DATACASS-512
public void shouldCountRecords() {
long count = personRepository.countByLastname("White");
assertThat(count).isEqualTo(3);
}
@Test // DATACASS-512
public void shouldApplyExistsProjection() {
assertThat(personRepository.existsByLastname("White")).isTrue();
assertThat(personRepository.existsByLastname("Schrader")).isFalse();
}
/**
* @author Mark Paluch
*/
@@ -346,6 +359,10 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
Person findByNumberOfChildren(NumberOfChildren numberOfChildren);
long countByLastname(String lastname);
boolean existsByLastname(String lastname);
Slice<Person> findAllSlicedByLastname(String lastname, Pageable pageable);
Collection<PersonProjection> findPersonProjectedBy();

View File

@@ -164,6 +164,26 @@ public class ReactiveCassandraRepositoryIntegrationTests extends AbstractKeyspac
.verifyComplete();
}
@Test // DATACASS-512
public void shouldCountRecords() {
StepVerifier.create(repository.countByLastname("Matthews")).expectNext(2L).verifyComplete();
StepVerifier.create(repository.countByLastname("None")).expectNext(0L).verifyComplete();
StepVerifier.create(repository.countQueryByLastname("Matthews")).expectNext(2L).verifyComplete();
StepVerifier.create(repository.countQueryByLastname("None")).expectNext(0L).verifyComplete();
}
@Test // DATACASS-512
public void shouldApplyExistsProjection() {
StepVerifier.create(repository.existsByLastname("Matthews")).expectNext(true).verifyComplete();
StepVerifier.create(repository.existsByLastname("None")).expectNext(false).verifyComplete();
StepVerifier.create(repository.existsQueryByLastname("Matthews")).expectNext(true).verifyComplete();
StepVerifier.create(repository.existsQueryByLastname("None")).expectNext(false).verifyComplete();
}
interface UserRepository extends ReactiveCassandraRepository<User, String> {
Flux<User> findByLastname(String lastname);
@@ -172,8 +192,18 @@ public class ReactiveCassandraRepositoryIntegrationTests extends AbstractKeyspac
Mono<User> findByLastname(Publisher<String> lastname);
Mono<Long> countByLastname(String lastname);
Mono<Boolean> existsByLastname(String lastname);
@Query("SELECT * FROM users WHERE lastname = ?0")
Flux<User> findStringQuery(Mono<String> lastname);
@CountQuery("SELECT COUNT(*) from users WHERE lastname = ?0")
Mono<Long> countQueryByLastname(String lastname);
@ExistsQuery("SELECT * from users WHERE lastname = ?0")
Mono<Boolean> existsQueryByLastname(String lastname);
}
interface GroupRepository extends ReactiveCassandraRepository<Group, GroupKey> {

View File

@@ -35,6 +35,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.domain.AllPossibleTypes;
import org.springframework.data.cassandra.repository.CountQuery;
import org.springframework.data.cassandra.repository.ExistsQuery;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
import org.springframework.data.cassandra.repository.support.AbstractSpringDataEmbeddedCassandraIntegrationTest;
@@ -269,6 +271,29 @@ public class RepositoryReturnTypesIntegrationTests extends AbstractSpringDataEmb
assertThat(result.get("biginteger")).isEqualTo((Object) BigInteger.ONE);
}
@Test // DATACASS-512
public void shouldApplyCountProjection() {
AllPossibleTypes entity = new AllPossibleTypes("123");
allPossibleTypesRepository.save(entity);
assertThat(allPossibleTypesRepository.countById(entity.getId())).isOne();
assertThat(allPossibleTypesRepository.countById("foo")).isZero();
}
@Test // DATACASS-512
public void shouldApplyExistsProjection() {
AllPossibleTypes entity = new AllPossibleTypes("123");
allPossibleTypesRepository.save(entity);
assertThat(allPossibleTypesRepository.existsUsingCountProjectionById(entity.getId())).isTrue();
assertThat(allPossibleTypesRepository.existsUsingCountProjectionById("foo")).isFalse();
assertThat(allPossibleTypesRepository.existsWithRowsById(entity.getId())).isTrue();
assertThat(allPossibleTypesRepository.existsWithRowsById("foo")).isFalse();
}
public interface AllPossibleTypesRepository extends CrudRepository<AllPossibleTypes, String> {
// blob/byte-buffer result do not work yet.
@@ -325,5 +350,14 @@ public class RepositoryReturnTypesIntegrationTests extends AbstractSpringDataEmb
@Query("select * from allpossibletypes where id = ?0")
Map<String, Object> findEntityAsMapById(String id);
@CountQuery("select COUNT(*) from allpossibletypes where id = ?0")
long countById(String id);
@ExistsQuery("select COUNT(*) from allpossibletypes where id = ?0")
boolean existsUsingCountProjectionById(String id);
@ExistsQuery("select * from allpossibletypes where id = ?0")
boolean existsWithRowsById(String id);
}
}

View File

@@ -207,6 +207,22 @@ public class PartTreeCassandraQueryUnitTests {
assertThat(statement.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_ONE);
}
@Test // DATACASS-512
public void shouldCreateCountQuery() {
Statement statement = deriveQueryFromMethod(Repo.class, "countBy", new Class[0]);
assertThat(statement.toString()).isEqualTo("SELECT COUNT(1) FROM person;");
}
@Test // DATACASS-512
public void shouldCreateExistsQuery() {
Statement statement = deriveQueryFromMethod(Repo.class, "existsBy", new Class[0]);
assertThat(statement.toString()).isEqualTo("SELECT * FROM person LIMIT 1;");
}
private String deriveQueryFromMethod(String method, Object... args) {
Class<?>[] types = new Class<?>[args.length];
@@ -277,6 +293,10 @@ public class PartTreeCassandraQueryUnitTests {
Person findByFirstnameIn(Collection<String> firstname);
long countBy();
boolean existsBy();
@AllowFiltering
Person findByFirstname(String firstname);

View File

@@ -15,14 +15,15 @@
*/
package org.springframework.data.cassandra.repository.query;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import java.lang.reflect.Method;
import java.util.Arrays;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Single;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Rule;
@@ -31,7 +32,6 @@ import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.cql.QueryOptions;
@@ -49,8 +49,6 @@ import org.springframework.util.ClassUtils;
import com.datastax.driver.core.ConsistencyLevel;
import com.datastax.driver.core.Statement;
import rx.Single;
/**
* Unit tests for {@link ReactivePartTreeCassandraQuery}.
*
@@ -135,6 +133,22 @@ public class ReactivePartTreeCassandraQueryUnitTests {
assertThat(statement.getConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_ONE);
}
@Test // DATACASS-512
public void shouldCreateCountQuery() {
Statement statement = deriveQueryFromMethod(PartTreeCassandraQueryUnitTests.Repo.class, "countBy", new Class[0]);
assertThat(statement.toString()).isEqualTo("SELECT COUNT(1) FROM person;");
}
@Test // DATACASS-512
public void shouldCreateExistsQuery() {
Statement statement = deriveQueryFromMethod(PartTreeCassandraQueryUnitTests.Repo.class, "existsBy", new Class[0]);
assertThat(statement.toString()).isEqualTo("SELECT * FROM person LIMIT 1;");
}
private String deriveQueryFromMethod(String method, Object... args) {
Class<?>[] types = new Class<?>[args.length];
@@ -186,6 +200,10 @@ public class ReactivePartTreeCassandraQueryUnitTests {
Flux<Person> findByFirstname(QueryOptions queryOptions, String firstname);
Mono<Long> countBy();
Mono<Boolean> existsBy();
@Consistency(ConsistencyLevel.LOCAL_ONE)
Flux<Person> findPersonBy();

View File

@@ -15,7 +15,8 @@
*/
package org.springframework.data.cassandra.repository.query;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
@@ -24,7 +25,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.cassandra.ReactiveSession;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
@@ -77,6 +77,8 @@ public class ReactiveStringBasedCassandraQueryUnitTests {
this.factory = new SpelAwareProxyProjectionFactory();
this.converter.afterPropertiesSet();
when(operations.getConverter()).thenReturn(converter);
}
@Test // DATACASS-335

View File

@@ -15,8 +15,8 @@
*/
package org.springframework.data.cassandra.repository.query;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -32,7 +32,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.cql.CqlIdentifier;
@@ -94,6 +93,8 @@ public class StringBasedCassandraQueryUnitTests {
this.factory = new SpelAwareProxyProjectionFactory();
this.converter.afterPropertiesSet();
when(operations.getConverter()).thenReturn(converter);
}
@Test // DATACASS-117

View File

@@ -3,6 +3,11 @@
This chapter summarizes changes and new features for each release.
[[new-features.2-1-0]]
== What's new in Spring Data for Apache Cassandra 2.1
* New annotations for `@CountQuery` and `@ExistsQuery`.
* Template API extended with `count(…)` and `exists(…)` methods accepting `Query`.
[[new-features.2-0-0]]
== What's new in Spring Data for Apache Cassandra 2.0