diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java index 09f8d81ab..37777ed0c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java @@ -121,7 +121,7 @@ public Mono exists() { } private String assembleEntityQuery(final boolean count) { - return query.toN1qlString(template, this.domainType, count); + return query.toN1qlSelectString(template, this.domainType, count); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java index 8dd080926..cb2c277cd 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java @@ -15,6 +15,7 @@ */ package org.springframework.data.couchbase.core; +import org.springframework.data.couchbase.core.support.TemplateUtils; import reactor.core.publisher.Flux; import java.util.Optional; @@ -58,14 +59,7 @@ static class ReactiveRemoveByQuerySupport implements ReactiveRemoveByQuery @Override public Flux all() { return Flux.defer(() -> { - String bucket = "`" + template.getBucketName() + "`"; - - String typeKey = template.getConverter().getTypeKey(); - String typeValue = template.support().getJavaNameForEntity(domainType); - String where = " WHERE `" + typeKey + "` = \"" + typeValue + "\""; - - String returning = " RETURNING meta().*"; - String statement = "DELETE FROM " + bucket + " " + where + returning; + String statement = assembleDeleteQuery(); return template.getCouchbaseClientFactory().getCluster().reactive().query(statement, buildQueryOptions()) .onErrorMap(throwable -> { @@ -75,7 +69,7 @@ public Flux all() { return throwable; } }).flatMapMany(ReactiveQueryResult::rowsAsObject) - .map(row -> new RemoveResult(row.getString("id"), row.getLong("cas"), Optional.empty())); + .map(row -> new RemoveResult(row.getString(TemplateUtils.SELECT_ID), row.getLong(TemplateUtils.SELECT_CAS), Optional.empty())); }); } @@ -97,6 +91,10 @@ public RemoveByQueryWithQuery consistentWith(final QueryScanConsistency scanC return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency); } + private String assembleDeleteQuery() { + return query.toN1qlRemoveString(template, this.domainType); + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/Query.java b/src/main/java/org/springframework/data/couchbase/core/query/Query.java index 5b154ec1f..6d699d404 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/Query.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/Query.java @@ -243,7 +243,7 @@ public String export() { return sb.toString(); } - public String toN1qlString(ReactiveCouchbaseTemplate template, Class domainClass, boolean isCount) { + public String toN1qlSelectString(ReactiveCouchbaseTemplate template, Class domainClass, boolean isCount) { StringBasedN1qlQueryParser.N1qlSpelValues n1ql = getN1qlSpelValues(template, domainClass, isCount); final StringBuilder statement = new StringBuilder(); appendString(statement, n1ql.selectEntity); // select ... @@ -254,6 +254,16 @@ public String toN1qlString(ReactiveCouchbaseTemplate template, Class domainClass return statement.toString(); } + public String toN1qlRemoveString(ReactiveCouchbaseTemplate template, Class domainClass) { + StringBasedN1qlQueryParser.N1qlSpelValues n1ql = getN1qlSpelValues(template, domainClass, false); + final StringBuilder statement = new StringBuilder(); + appendString(statement, n1ql.delete); // delete ... + appendWhereString(statement, n1ql.filter); // typeKey = typeValue + appendWhere(statement, null); // criteria on this Query + appendString(statement, n1ql.returning); + return statement.toString(); + } + StringBasedN1qlQueryParser.N1qlSpelValues getN1qlSpelValues(ReactiveCouchbaseTemplate template, Class domainClass, boolean isCount) { String typeKey = template.getConverter().getTypeKey(); diff --git a/src/main/java/org/springframework/data/couchbase/core/query/StringQuery.java b/src/main/java/org/springframework/data/couchbase/core/query/StringQuery.java index cb9012970..f940b54d2 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/StringQuery.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/StringQuery.java @@ -52,7 +52,7 @@ private void appendInlineN1qlStatement(final StringBuilder sb) { } @Override - public String toN1qlString(ReactiveCouchbaseTemplate template, Class domainClass, boolean isCount) { + public String toN1qlSelectString(ReactiveCouchbaseTemplate template, Class domainClass, boolean isCount) { final StringBuilder statement = new StringBuilder(); appendInlineN1qlStatement(statement); // apply the string statement // To use generated parameters for literals diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java index 64be7d9d1..5669ea8bc 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java @@ -32,9 +32,10 @@ import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.dao.DataRetrievalFailureException; import org.springframework.data.couchbase.CouchbaseClientFactory; import org.springframework.data.couchbase.SimpleCouchbaseClientFactory; +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.couchbase.core.query.QueryCriteria; import org.springframework.data.couchbase.domain.Config; import org.springframework.data.couchbase.domain.NaiveAuditorAware; import org.springframework.data.couchbase.domain.User; @@ -46,13 +47,13 @@ import com.couchbase.client.core.error.IndexExistsException; import com.couchbase.client.java.query.QueryScanConsistency; -import reactor.core.publisher.Mono; /** * Query tests Theses tests rely on a cb server running * * @author Michael Nitschinger * @author Michael Reiche + * @author Haris Alesevic */ @IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED) class CouchbaseTemplateQueryIntegrationTests extends ClusterAwareIntegrationTests { @@ -82,10 +83,12 @@ void beforeEach() { ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class); couchbaseTemplate = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE); reactiveCouchbaseTemplate = (ReactiveCouchbaseTemplate) ac.getBean(REACTIVE_COUCHBASE_TEMPLATE); + // ensure each test starts with clean state + couchbaseTemplate.removeByQuery(User.class).all(); } @Test - void findByQuery() { + void findByQueryAll() { try { User user1 = new User(UUID.randomUUID().toString(), "user1", "user1"); User user2 = new User(UUID.randomUUID().toString(), "user2", "user2"); @@ -128,7 +131,7 @@ void findByQuery() { } @Test - void removeByQuery() { + void removeByQueryAll() { User user1 = new User(UUID.randomUUID().toString(), "user1", "user1"); User user2 = new User(UUID.randomUUID().toString(), "user2", "user2"); @@ -144,4 +147,28 @@ void removeByQuery() { } + @Test + void removeByMatchingQuery() { + User user1 = new User(UUID.randomUUID().toString(), "user1", "user1"); + User user2 = new User(UUID.randomUUID().toString(), "user2", "user2"); + User specialUser = new User(UUID.randomUUID().toString(), "special", "special"); + + couchbaseTemplate.upsertById(User.class).all(Arrays.asList(user1, user2, specialUser)); + + assertTrue(couchbaseTemplate.existsById().one(user1.getId())); + assertTrue(couchbaseTemplate.existsById().one(user2.getId())); + assertTrue(couchbaseTemplate.existsById().one(specialUser.getId())); + + Query nonSpecialUsers = new Query(QueryCriteria.where("firstname").notLike("special")); + + couchbaseTemplate.removeByQuery(User.class).consistentWith(QueryScanConsistency.REQUEST_PLUS) + .matching(nonSpecialUsers) + .all(); + + assertNull(couchbaseTemplate.findById(User.class).one(user1.getId())); + assertNull(couchbaseTemplate.findById(User.class).one(user2.getId())); + assertNotNull(couchbaseTemplate.findById(User.class).one(specialUser.getId())); + + } + } diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java index 7b42450d2..4129771ed 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java @@ -22,22 +22,16 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.couchbase.domain.Airline; -import org.springframework.data.couchbase.domain.AirlineRepository; import org.springframework.data.couchbase.domain.User; import org.springframework.data.couchbase.domain.UserRepository; import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; -import org.springframework.data.couchbase.util.Capabilities; import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests; -import org.springframework.data.couchbase.util.ClusterType; -import org.springframework.data.couchbase.util.IgnoreWhen; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.core.NamedQueries; @@ -46,9 +40,7 @@ import org.springframework.data.repository.query.*; import java.lang.reflect.Method; -import java.util.Optional; import java.util.Properties; -import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; @@ -88,7 +80,7 @@ void createsQueryCorrectly() throws Exception { Query query = creator.createQuery(); assertEquals( "SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `travel-sample`.* FROM `travel-sample` where `_class` = \"org.springframework.data.couchbase.domain.User\" and firstname = $1 and lastname = $2", - query.toN1qlString(couchbaseTemplate.reactive(), User.class, false)); + query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false)); } @Test @@ -106,7 +98,7 @@ void createsQueryCorrectly2() throws Exception { Query query = creator.createQuery(); assertEquals( "SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `travel-sample`.* FROM `travel-sample` where `_class` = \"org.springframework.data.couchbase.domain.User\" and (firstname = $first or lastname = $last)", - query.toN1qlString(couchbaseTemplate.reactive(), User.class, false)); + query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false)); } @Test diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorTests.java index 640ff3079..9681255d4 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorTests.java @@ -25,7 +25,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; @@ -91,7 +90,7 @@ void findUsingStringNq1l() throws Exception { queryMethod, converter, config().bucketname(), QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); Query query = creator.createQuery(); - System.out.println(query.toN1qlString(couchbaseTemplate.reactive(), Airline.class, false)); + System.out.println(query.toN1qlSelectString(couchbaseTemplate.reactive(), Airline.class, false)); try { Thread.sleep(3000);