diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java
index 99dcb21e1..b84fc529b 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java
@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
+import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -394,7 +395,41 @@ public class Criteria implements CriteriaDefinition {
* @see MongoDB Query operator: $not
*/
public Criteria not() {
- return not(null);
+ return not((Object)null);
+ }
+
+ public Criteria not(Consumer source) {
+
+ Criteria sink = new Criteria(this.key);
+ source.accept(sink);
+
+ List target = new ArrayList<>();
+ for(Criteria criteria : sink.criteriaChain) {
+ boolean nextIsNot = false;
+ for(Entry entry : criteria.criteria.entrySet()) {
+ if(entry.getKey().equals("$not")) {
+ nextIsNot = true;
+ continue;
+ }
+
+ Criteria extractedSingleCriteria = new Criteria(criteria.key);
+ if(nextIsNot) {
+ extractedSingleCriteria = new Criteria("$not");
+ extractedSingleCriteria.criteria.put(criteria.key, new Document(entry.getKey(), entry.getValue()));
+ nextIsNot = false;
+ } else {
+ extractedSingleCriteria.criteria.put(entry.getKey(), entry.getValue());
+ }
+ target.add(extractedSingleCriteria);
+ }
+ }
+ List bsonList = createCriteriaList(target.toArray(new Criteria[0]), true);
+ return registerCriteriaChainElement(new Criteria("$and").is(bsonList));
+ }
+
+ public Criteria not(Criteria... criteria) {
+ List bsonList = createCriteriaList(criteria, true);
+ return registerCriteriaChainElement(new Criteria("$and").is(bsonList));
}
/**
@@ -660,7 +695,7 @@ public class Criteria implements CriteriaDefinition {
* @param criteria
*/
public Criteria orOperator(Criteria... criteria) {
- BasicDBList bsonList = createCriteriaList(criteria);
+ List bsonList = createCriteriaList(criteria, false);
return registerCriteriaChainElement(new Criteria("$or").is(bsonList));
}
@@ -674,7 +709,7 @@ public class Criteria implements CriteriaDefinition {
* @param criteria
*/
public Criteria norOperator(Criteria... criteria) {
- BasicDBList bsonList = createCriteriaList(criteria);
+ List bsonList = createCriteriaList(criteria, false);
return registerCriteriaChainElement(new Criteria("$nor").is(bsonList));
}
@@ -688,7 +723,7 @@ public class Criteria implements CriteriaDefinition {
* @param criteria
*/
public Criteria andOperator(Criteria... criteria) {
- BasicDBList bsonList = createCriteriaList(criteria);
+ List bsonList = createCriteriaList(criteria, false);
return registerCriteriaChainElement(new Criteria("$and").is(bsonList));
}
@@ -775,16 +810,23 @@ public class Criteria implements CriteriaDefinition {
queryCriteria.put(this.key, this.isValue);
queryCriteria.putAll(document);
} else {
- queryCriteria.put(this.key, document);
+ if(!document.isEmpty()) {
+ queryCriteria.put(this.key, document);
+ }
}
return queryCriteria;
}
- private BasicDBList createCriteriaList(Criteria[] criteria) {
- BasicDBList bsonList = new BasicDBList();
+ private List createCriteriaList(Criteria[] criteria, boolean not) {
+ List bsonList = new ArrayList();
for (Criteria c : criteria) {
- bsonList.add(c.getCriteriaObject());
+
+ Document co = c.getCriteriaObject();
+ if(not) {
+ co = new Document("$not", co);
+ }
+ bsonList.add(co);
}
return bsonList;
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java
index 6e4b92086..9976c415a 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java
@@ -19,6 +19,8 @@ import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
+import java.util.function.Consumer;
+
import org.bson.Document;
import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.ProxyFactory;
@@ -350,6 +352,44 @@ class QueryTests {
compareQueries(target, source);
}
+
+ @Test // DATAMONGO-2499
+ void notCombiningSeveralIndedependentCriterias() {
+
+ Query source = new Query(new Criteria().not(Criteria.where("age").gt(30), Criteria.where("age").lt(20)));
+ Document target = source.getQueryObject();
+
+ assertThat(target).isEqualTo(Document.parse("{\"$and\": [{\"$not\": {\"age\": {\"$gt\": 30}}}, {\"$not\": {\"age\": {\"$lt\": 20}}}]}"));
+ }
+
+ @Test // DATAMONGO-2499
+ void notCombiningSingleCriteria() {
+
+ Query source = new Query(Criteria.where("age").not(age -> age.gt(30).lt(20)));
+ Document target = source.getQueryObject();
+
+ assertThat(target).isEqualTo(Document.parse("{\"$and\": [{\"$not\": {\"age\": {\"$gt\": 30}}}, {\"$not\": {\"age\": {\"$lt\": 20}}}]}"));
+ }
+
+ @Test // DATAMONGO-2499
+ void notCombiningSingleCriteriaWithAnother() {
+
+ Query source = new Query(Criteria.where("age").not(age -> age.gt(30).lt(20)).exists(true));
+ Document target = source.getQueryObject();
+
+ assertThat(target).isEqualTo(Document.parse("{\"$and\": [{\"$not\": {\"age\": {\"$gt\": 30}}}, {\"$not\": {\"age\": {\"$lt\": 20}}}], \"age\": {\"$exists\": true}}"));
+ }
+
+ @Test // DATAMONGO-2499
+ void notCombiningSingleCriteriaWithNestedNot() {
+
+ Query source = new Query(Criteria.where("age").not(age -> age.gt(30).not().lt(20)));
+ Document target = source.getQueryObject();
+
+ assertThat(target).isEqualTo(Document.parse("{\"$and\": [{\"$not\": {\"age\": {\"$gt\": 30}}}, {\"$not\": { \"$not\": {\"age\": {\"$lt\": 20}}}}]}"));
+ }
+
+
private void compareQueries(Query actual, Query expected) {
assertThat(actual.getCollation()).isEqualTo(expected.getCollation());