Skip to content

Commit b4afe55

Browse files
committed
Simplify AndFilter to always use $and
JAVA-3338
1 parent 664d066 commit b4afe55

File tree

4 files changed

+61
-72
lines changed

4 files changed

+61
-72
lines changed

driver-core/src/main/com/mongodb/client/model/Filters.java

Lines changed: 3 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -988,65 +988,12 @@ private static class AndFilter implements Bson {
988988

989989
@Override
990990
public <TDocument> BsonDocument toBsonDocument(final Class<TDocument> documentClass, final CodecRegistry codecRegistry) {
991-
BsonDocument andRenderable = new BsonDocument();
992-
991+
BsonArray clauses = new BsonArray();
993992
for (Bson filter : filters) {
994-
BsonDocument renderedRenderable = filter.toBsonDocument(documentClass, codecRegistry);
995-
for (Map.Entry<String, BsonValue> element : renderedRenderable.entrySet()) {
996-
addClause(andRenderable, element);
997-
}
993+
clauses.add(filter.toBsonDocument(documentClass, codecRegistry));
998994
}
999995

1000-
if (andRenderable.isEmpty()) {
1001-
andRenderable.append("$and", new BsonArray());
1002-
}
1003-
1004-
return andRenderable;
1005-
}
1006-
1007-
private void addClause(final BsonDocument document, final Map.Entry<String, BsonValue> clause) {
1008-
if (clause.getKey().equals("$and")) {
1009-
for (BsonValue value : clause.getValue().asArray()) {
1010-
for (Map.Entry<String, BsonValue> element : value.asDocument().entrySet()) {
1011-
addClause(document, element);
1012-
}
1013-
}
1014-
} else if (document.size() == 1 && document.keySet().iterator().next().equals("$and")) {
1015-
document.get("$and").asArray().add(new BsonDocument(clause.getKey(), clause.getValue()));
1016-
} else if (document.containsKey(clause.getKey())) {
1017-
if (document.get(clause.getKey()).isDocument() && clause.getValue().isDocument()) {
1018-
BsonDocument existingClauseValue = document.get(clause.getKey()).asDocument();
1019-
BsonDocument clauseValue = clause.getValue().asDocument();
1020-
if (keysIntersect(clauseValue, existingClauseValue)) {
1021-
promoteRenderableToDollarForm(document, clause);
1022-
} else {
1023-
existingClauseValue.putAll(clauseValue);
1024-
}
1025-
} else {
1026-
promoteRenderableToDollarForm(document, clause);
1027-
}
1028-
} else {
1029-
document.append(clause.getKey(), clause.getValue());
1030-
}
1031-
}
1032-
1033-
private boolean keysIntersect(final BsonDocument first, final BsonDocument second) {
1034-
for (String name : first.keySet()) {
1035-
if (second.containsKey(name)) {
1036-
return true;
1037-
}
1038-
}
1039-
return false;
1040-
}
1041-
1042-
private void promoteRenderableToDollarForm(final BsonDocument document, final Map.Entry<String, BsonValue> clause) {
1043-
BsonArray clauses = new BsonArray();
1044-
for (Map.Entry<String, BsonValue> queryElement : document.entrySet()) {
1045-
clauses.add(new BsonDocument(queryElement.getKey(), queryElement.getValue()));
1046-
}
1047-
clauses.add(new BsonDocument(clause.getKey(), clause.getValue()));
1048-
document.clear();
1049-
document.put("$and", clauses);
996+
return new BsonDocument("$and", clauses);
1050997
}
1051998

1052999
@Override

driver-core/src/test/functional/com/mongodb/client/model/FiltersFunctionalSpecification.groovy

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,13 @@ class FiltersFunctionalSpecification extends OperationFunctionalSpecification {
187187
find(and([lt('x', 4), lt('x', 3)])) == [a, b]
188188
}
189189

190+
def 'explicit $and when using $not'() {
191+
expect:
192+
find(and([lt('x', 3), not(lt('x', 1))])) == [a, b]
193+
find(and([lt('x', 5), gt('x', 0), not(gt('x', 2))])) == [a, b]
194+
find(and([not(lt('x', 2)), lt('x', 4), not(gt('x', 2))])) == [b]
195+
}
196+
190197
def 'should render $all'() {
191198
expect:
192199
find(all('a', [1, 2])) == [a]

driver-core/src/test/unit/com/mongodb/client/model/FiltersSpecification.groovy

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -153,28 +153,62 @@ class FiltersSpecification extends Specification {
153153
toBson(and()) == parse('{$and : []}')
154154
}
155155

156-
def 'and should render and without using $and'() {
156+
def 'and should render using $and'() {
157157
expect:
158-
toBson(and([eq('x', 1), eq('y', 2)])) == parse('{x : 1, y : 2}')
159-
toBson(and(eq('x', 1), eq('y', 2))) == parse('{x : 1, y : 2}')
158+
toBson(and([eq('x', 1), eq('y', 2)])) == parse('{$and: [{x : 1}, {y : 2}]}')
159+
toBson(and(eq('x', 1), eq('y', 2))) == parse('{$and: [{x : 1}, {y : 2}]}')
160160
}
161161

162162
def 'and should render $and with clashing keys'() {
163163
expect:
164164
toBson(and([eq('a', 1), eq('a', 2)])) == parse('{$and: [{a: 1}, {a: 2}]}');
165165
}
166166

167-
def 'and should flatten multiple operators for the same key'() {
168-
expect:
169-
toBson(and([gt('a', 1), lt('a', 9)])) == parse('{a : {$gt : 1, $lt : 9}}');
170-
}
171-
172-
def 'and should flatten nested'() {
173-
expect:
174-
toBson(and([and([eq('a', 1), eq('b', 2)]), eq('c', 3)])) == parse('{a : 1, b : 2, c : 3}')
175-
toBson(and([and([eq('a', 1), eq('a', 2)]), eq('c', 3)])) == parse('{$and:[{a : 1}, {a : 2}, {c : 3}] }')
176-
toBson(and([lt('a', 1), lt('b', 2)])) == parse('{a : {$lt : 1}, b : {$lt : 2} }')
177-
toBson(and([lt('a', 1), lt('a', 2)])) == parse('{$and : [{a : {$lt : 1}}, {a : {$lt : 2}}]}')
167+
def 'and should not flatten nested'() {
168+
expect:
169+
toBson(and([and([eq('a', 1), eq('b', 2)]), eq('c', 3)])) ==
170+
parse('{$and: [{$and: [{a : 1}, {b : 2}]}, {c : 3}]}')
171+
toBson(and([and([eq('a', 1), eq('a', 2)]), eq('c', 3)])) ==
172+
parse('{$and:[{$and: [{a : 1}, {a : 2}]}, {c : 3}]} }')
173+
toBson(and([lt('a', 1), lt('b', 2)])) ==
174+
parse('{$and: [{a : {$lt : 1}}, {b : {$lt : 2}}]}')
175+
toBson(and([lt('a', 1), lt('a', 2)])) ==
176+
parse('{$and : [{a : {$lt : 1}}, {a : {$lt : 2}}]}')
177+
}
178+
179+
def '$and should be explicit when using $not'() {
180+
expect:
181+
toBson(and(lt('item', 10), not(lt('item', 5)))) ==
182+
parse('''{
183+
$and:
184+
[
185+
{ item: { $lt: 10 } },
186+
{ item: { $not: { $lt: 5 } } }
187+
]
188+
}
189+
''')
190+
191+
toBson(and(lt('item', 100), gt('item', 10), not(gt('item', 50)))) ==
192+
parse('''{
193+
$and:
194+
[
195+
{ item: { $lt: 100 } },
196+
{ item: { $gt: 10 } },
197+
{ item: { $not: { $gt: 50 } } }
198+
]
199+
}
200+
''')
201+
202+
toBson(and(not(lt('item', 10)), lt('item', 100), not(gt('item', 50)))) ==
203+
parse('''{
204+
$and:
205+
[
206+
{ item: { $not: { $lt: 10 } } },
207+
{ item: { $lt: 100 } },
208+
{ item: { $not: { $gt: 50 } } }
209+
]
210+
}
211+
''')
178212
}
179213

180214
def 'should render $all'() {
@@ -189,7 +223,7 @@ class FiltersSpecification extends Specification {
189223
parse('{results : {$elemMatch : {$gte: 80, $lt: 85}}}')
190224

191225
toBson(elemMatch('results', and(eq('product', 'xyz'), gt('score', 8)))) ==
192-
parse('{ results : {$elemMatch : {product : "xyz", score : {$gt : 8}}}}')
226+
parse('{ results : {$elemMatch : {$and: [{product : "xyz"}, {score : {$gt : 8}}]}}}')
193227
}
194228

195229
def 'should render $in'() {

driver-core/src/test/unit/com/mongodb/client/model/ProjectionsSpecification.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ class ProjectionsSpecification extends Specification {
6161

6262
def 'elemMatch'() {
6363
expect:
64-
toBson(elemMatch('x', and(eq('y', 1), eq('z', 2)))) == parse('{x : {$elemMatch : {y : 1, z : 2}}}')
64+
toBson(elemMatch('x', and(eq('y', 1), eq('z', 2)))) ==
65+
parse('{x : {$elemMatch : {$and: [{y : 1}, {z : 2}]}}}')
6566
}
6667

6768
def 'slice'() {

0 commit comments

Comments
 (0)