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:
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
@@ -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 "";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user