Skip to content

Commit e045c75

Browse files
fhassakjyemin
authored andcommitted
JAVA-2635: Add restrictSearchWithMatch to GraphLookupOptions (#422)
1 parent cfc43df commit e045c75

File tree

6 files changed

+123
-6
lines changed

6 files changed

+123
-6
lines changed

docs/reference/content/builders/aggregation.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,14 @@ graphLookup("contacts", "$friends", "friends", "name", "socialNetwork",
180180

181181
Using `GraphLookupOptions`, the output can be tailored to restrict the depth of the recursion as well to inject a field containing the depth of the recursion at which a document was included.
182182

183+
The recursive search can be filtered by specifying additional conditions.
184+
185+
```java
186+
graphLookup("contacts", "$friends", "friends", "name", "socialNetwork",
187+
new GraphLookupOptions().maxDepth(1).restrictSearchWithMatch(Filters.eq("hobbies","golf"))
188+
```
189+
190+
183191
### SortByCount
184192

185193
The [`$sortByCount`]({{< docsref "reference/operator/aggregation/sortByCount/" >}}) stage groups documents by a given expression and then sorts these groups by count in descending order. The `sortByCount` outputs documents that contains an `_id` field, which contains the discrete values of the given expression, and the `count` field that contains the number of documents that fall into that group.

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,10 @@ public <TDocument> BsonDocument toBsonDocument(final Class<TDocument> tDocumentC
580580
if (options.getDepthField() != null) {
581581
writer.writeString("depthField", options.getDepthField());
582582
}
583+
if (options.getRestrictSearchWithMatch() != null) {
584+
writer.writeName("restrictSearchWithMatch");
585+
BuildersHelper.encodeValue(writer, options.getRestrictSearchWithMatch(), codecRegistry);
586+
}
583587

584588
writer.writeEndDocument();
585589

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package com.mongodb.client.model;
1818

19+
import org.bson.conversions.Bson;
20+
1921
/**
2022
* The options for a graphLookup aggregation pipeline stage
2123
*
@@ -26,6 +28,7 @@
2628
public final class GraphLookupOptions {
2729
private Integer maxDepth;
2830
private String depthField;
31+
private Bson restrictSearchWithMatch;
2932

3033
/**
3134
* The name of the field in which to store the depth value
@@ -63,6 +66,26 @@ public Integer getMaxDepth() {
6366
return maxDepth;
6467
}
6568

69+
/**
70+
* A document specifying additional conditions for the recursive search
71+
*
72+
* @param filter the filter expression
73+
* @return this
74+
* @since 3.6
75+
*/
76+
public GraphLookupOptions restrictSearchWithMatch(final Bson filter) {
77+
restrictSearchWithMatch = filter;
78+
return this;
79+
}
80+
81+
/**
82+
* @return the filter expression
83+
* @since 3.6
84+
*/
85+
public Bson getRestrictSearchWithMatch() {
86+
return restrictSearchWithMatch;
87+
}
88+
6689
@Override
6790
public String toString() {
6891
StringBuilder stringBuilder = new StringBuilder()
@@ -79,7 +102,16 @@ public String toString() {
79102
stringBuilder
80103
.append("maxDepth=")
81104
.append(maxDepth);
105+
if (restrictSearchWithMatch != null) {
106+
stringBuilder.append(", ");
107+
}
108+
}
109+
if (restrictSearchWithMatch != null) {
110+
stringBuilder
111+
.append("restrictSearchWithMatch=")
112+
.append(restrictSearchWithMatch);
82113
}
114+
83115
return stringBuilder
84116
.append('}')
85117
.toString();

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

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import static com.mongodb.client.model.Aggregates.skip
5252
import static com.mongodb.client.model.Aggregates.sort
5353
import static com.mongodb.client.model.Aggregates.sortByCount
5454
import static com.mongodb.client.model.Aggregates.unwind
55+
import static com.mongodb.client.model.Filters.eq
5556
import static com.mongodb.client.model.Filters.exists
5657
import static com.mongodb.client.model.Projections.computed
5758
import static com.mongodb.client.model.Projections.exclude
@@ -339,7 +340,7 @@ class AggregatesFunctionalSpecification extends OperationFunctionalSpecification
339340
}
340341

341342
@IgnoreIf({ !serverVersionAtLeast(3, 4) })
342-
def '$graphLookup with options'() {
343+
def '$graphLookup with depth options'() {
343344
given:
344345
def fromCollectionName = 'contacts'
345346
def fromHelper = getCollectionHelper(new MongoNamespace(getDatabaseName(), fromCollectionName))
@@ -379,6 +380,57 @@ class AggregatesFunctionalSpecification extends OperationFunctionalSpecification
379380
fromHelper?.drop()
380381
}
381382

383+
@IgnoreIf({ !serverVersionAtLeast(3, 4) })
384+
def '$graphLookup with query filter option'() {
385+
given:
386+
def fromCollectionName = 'contacts'
387+
def fromHelper = getCollectionHelper(new MongoNamespace(getDatabaseName(), fromCollectionName))
388+
389+
fromHelper.drop()
390+
391+
fromHelper.insertDocuments(
392+
Document.parse('''{ _id: 0, name: "Bob Smith", friends: ["Anna Jones", "Chris Green"],
393+
hobbies : ["tennis", "unicycling", "golf"] }'''),
394+
Document.parse('''{ _id: 1, name: "Anna Jones", friends: ["Bob Smith", "Chris Green", "Joe Lee"],
395+
hobbies : ["archery", "golf", "woodworking"] }'''),
396+
Document.parse('''{ _id: 2, name: "Chris Green", friends: ["Anna Jones", "Bob Smith"],
397+
hobbies : ["knitting", "frisbee"] }'''),
398+
Document.parse('''{ _id: 3, name: "Joe Lee", friends: ["Anna Jones", "Fred Brown"],
399+
hobbies : [ "tennis", "golf", "topiary" ] }'''),
400+
Document.parse('''{ _id: 4, name: "Fred Brown", friends: ["Joe Lee"],
401+
hobbies : [ "travel", "ceramics", "golf" ] }'''))
402+
403+
404+
def lookupDoc = graphLookup('contacts', new BsonString('$friends'), 'friends', 'name', 'golfers',
405+
new GraphLookupOptions()
406+
.restrictSearchWithMatch(eq('hobbies', 'golf')))
407+
408+
when:
409+
def results = fromHelper.aggregate([lookupDoc,
410+
unwind('$golfers'),
411+
sort(new Document('_id', 1)
412+
.append('golfers._id', 1))])
413+
414+
then:
415+
results.subList(0, 4) == [
416+
Document.parse('''{ _id: 0, name: "Bob Smith", friends: ["Anna Jones", "Chris Green"],
417+
hobbies : ["tennis", "unicycling", "golf"], golfers: {_id: 0, name: "Bob Smith",
418+
friends: ["Anna Jones", "Chris Green"], hobbies : ["tennis", "unicycling", "golf"] } }'''),
419+
Document.parse('''{ _id: 0, name: "Bob Smith", friends: ["Anna Jones", "Chris Green"],
420+
hobbies: ["tennis", "unicycling", "golf"], golfers:{ _id: 1, name: "Anna Jones",
421+
friends: ["Bob Smith", "Chris Green", "Joe Lee"], hobbies : ["archery", "golf", "woodworking"] } } }'''),
422+
Document.parse('''{ _id: 0, name: "Bob Smith", friends: ["Anna Jones", "Chris Green"],
423+
hobbies: ["tennis", "unicycling", "golf"], golfers: { _id: 3, name: "Joe Lee",
424+
friends: ["Anna Jones", "Fred Brown"], hobbies : [ "tennis", "golf", "topiary" ] } }'''),
425+
Document.parse('''{ _id: 0, name: "Bob Smith", friends: ["Anna Jones", "Chris Green"],
426+
hobbies: ["tennis", "unicycling", "golf"], golfers:{ _id: 4, name: "Fred Brown", friends: ["Joe Lee"],
427+
hobbies : [ "travel", "ceramics", "golf" ] } }''')
428+
]
429+
430+
cleanup:
431+
fromHelper?.drop()
432+
}
433+
382434
@IgnoreIf({ !serverVersionAtLeast(3, 4) })
383435
def '$bucket'() {
384436
given:

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -233,21 +233,38 @@ class AggregatesSpecification extends Specification {
233233

234234
def 'should render $graphLookup'() {
235235
expect:
236+
//without options
236237
toBson(graphLookup('contacts', '$friends', 'friends', 'name', 'socialNetwork')) ==
237238
parse('''{ $graphLookup: { from: "contacts", startWith: "$friends", connectFromField: "friends", connectToField: "name",
238239
as: "socialNetwork" } }''')
240+
241+
//with maxDepth
239242
toBson(graphLookup('contacts', '$friends', 'friends', 'name', 'socialNetwork', new GraphLookupOptions().maxDepth(1))) ==
240243
parse('''{ $graphLookup: { from: "contacts", startWith: "$friends", connectFromField: "friends", connectToField: "name",
241244
as: "socialNetwork", maxDepth: 1 } }''')
245+
246+
// with depthField
247+
toBson(graphLookup('contacts', '$friends', 'friends', 'name', 'socialNetwork', new GraphLookupOptions().depthField('master'))) ==
248+
parse('''{ $graphLookup: { from: "contacts", startWith: "$friends", connectFromField: "friends", connectToField: "name",
249+
as: "socialNetwork", depthField: "master" } }''')
250+
251+
// with restrictSearchWithMatch
242252
toBson(graphLookup('contacts', '$friends', 'friends', 'name', 'socialNetwork', new GraphLookupOptions()
243-
.maxDepth(1)
244-
.depthField('master'))) ==
253+
.restrictSearchWithMatch(Filters.eq('hobbies', 'golf')))) ==
254+
parse('''{ $graphLookup: { from: "contacts", startWith: "$friends", connectFromField: "friends", connectToField: "name",
255+
as: "socialNetwork", restrictSearchWithMatch : { "hobbies" : "golf" } } }''')
256+
257+
// with maxDepth and depthField
258+
toBson(graphLookup('contacts', '$friends', 'friends', 'name', 'socialNetwork', new GraphLookupOptions()
259+
.maxDepth(1).depthField('master'))) ==
245260
parse('''{ $graphLookup: { from: "contacts", startWith: "$friends", connectFromField: "friends", connectToField: "name",
246261
as: "socialNetwork", maxDepth: 1, depthField: "master" } }''')
262+
263+
// with all options
247264
toBson(graphLookup('contacts', '$friends', 'friends', 'name', 'socialNetwork', new GraphLookupOptions()
248-
.depthField('master'))) ==
249-
parse('''{ $graphLookup: { from: "contacts", startWith: "$friends", connectFromField: "friends", connectToField: "name",
250-
as: "socialNetwork", depthField: "master" } }''')
265+
.maxDepth(1).depthField('master').restrictSearchWithMatch(Filters.eq('hobbies', 'golf')))) ==
266+
parse('''{ $graphLookup: { from: "contacts", startWith: "$friends", connectFromField: "friends", connectToField: "name",
267+
as: "socialNetwork", maxDepth: 1, depthField: "master", restrictSearchWithMatch : { "hobbies" : "golf" } } }''')
251268
}
252269

253270
def 'should render $skip'() {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@ package com.mongodb.client.model
1818

1919
import spock.lang.Specification
2020

21+
import static org.bson.BsonDocument.parse
22+
2123
class GraphLookupOptionsSpecification extends Specification {
2224
def "should return new options with the same property values"() {
2325
when:
2426
def options = new GraphLookupOptions()
2527
.maxDepth(10)
2628
.depthField('field')
29+
.restrictSearchWithMatch(parse('{x : 1}'))
2730

2831
then:
2932
options.maxDepth == 10
3033
options.depthField == 'field'
34+
options.restrictSearchWithMatch == parse('{x : 1}')
3135
}
3236
}

0 commit comments

Comments
 (0)