Skip to content

Commit 72dc5ec

Browse files
committed
CSHARP-1649: Added support for $graphLookup aggregation stage.
1 parent 4e06ffa commit 72dc5ec

12 files changed

+1264
-0
lines changed

src/MongoDB.Driver.Core/Core/Misc/Feature.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class Feature
3030
private static readonly Feature __aggregateCursorResult = new Feature("AggregateCursorResult", new SemanticVersion(2, 6, 0));
3131
private static readonly Feature __aggregateExplain = new Feature("AggregateExplain", new SemanticVersion(2, 6, 0));
3232
private static readonly Feature __aggregateFacetStage = new Feature("AggregateFacetStage", new SemanticVersion(3, 4, 0, "rc0"));
33+
private static readonly Feature __aggregateGraphLookupStage = new Feature("AggregateGraphLookupStage", new SemanticVersion(3, 4, 0, "rc0"));
3334
private static readonly Feature __aggregateOut = new Feature("Aggregate", new SemanticVersion(2, 6, 0));
3435
private static readonly Feature __bypassDocumentValidation = new Feature("BypassDocumentValidation", new SemanticVersion(3, 2, 0));
3536
private static readonly CollationFeature __collation = new CollationFeature("Collation", new SemanticVersion(3, 3, 11));
@@ -88,6 +89,11 @@ public class Feature
8889
/// </summary>
8990
public static Feature AggregateFacetStage => __aggregateFacetStage;
9091

92+
/// <summary>
93+
/// Gets the aggregate $graphLookup stage feature.
94+
/// </summary>
95+
public static Feature AggregateGraphLookupStage => __aggregateGraphLookupStage;
96+
9197
/// <summary>
9298
/// Gets the aggregate out feature.
9399
/// </summary>

src/MongoDB.Driver/AggregateFluent.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using System;
1717
using System.Collections.Generic;
1818
using System.Linq;
19+
using System.Reflection;
1920
using System.Text;
2021
using System.Threading;
2122
using System.Threading.Tasks;
@@ -255,6 +256,60 @@ public override IAggregateFluent<TNewResult> Facet<TNewResult>(
255256
return AppendStage<TNewResult>(stage);
256257
}
257258

259+
public override IAggregateFluent<TNewResult> GraphLookup<TNewResult, TFrom, TConnect, TConnectFrom, TStartWith, TAs, TAsEnumerable>(
260+
IMongoCollection<TFrom> from,
261+
FieldDefinition<TFrom, TConnectFrom> connectFromField,
262+
FieldDefinition<TFrom, TConnect> connectToField,
263+
AggregateExpressionDefinition<TResult, TStartWith> startWith,
264+
FieldDefinition<TNewResult, TAsEnumerable> @as,
265+
FieldDefinition<TAs, int> depthField,
266+
AggregateGraphLookupOptions<TNewResult, TFrom, TConnect, TConnectFrom, TStartWith, TAs, TAsEnumerable> options = null)
267+
{
268+
Ensure.IsNotNull(from, nameof(from));
269+
Ensure.IsNotNull(connectFromField, nameof(connectFromField));
270+
Ensure.IsNotNull(connectToField, nameof(connectToField));
271+
Ensure.IsNotNull(startWith, nameof(startWith));
272+
Ensure.IsNotNull(@as, nameof(@as));
273+
Ensure.That(from.Database.DatabaseNamespace.Equals(_collection.Database.DatabaseNamespace), "From collection must be from the same database.", nameof(from));
274+
Ensure.That(IsTConnectOrEnumerableTConnect<TConnectFrom, TConnect>(), "TConnectFrom must be either TConnect or a type that implements IEnumerable<TConnect>.", nameof(TConnectFrom));
275+
Ensure.That(IsTConnectOrEnumerableTConnect<TStartWith, TConnect>(), "TStartWith must be either TConnect or a type that implements IEnumerable<TConnect>.", nameof(TStartWith));
276+
277+
const string operatorName = "$graphLookup";
278+
var stage = new DelegatedPipelineStageDefinition<TResult, TNewResult>(
279+
operatorName,
280+
(s, sr) =>
281+
{
282+
var resultSerializer = s;
283+
var newResultSerializer = options?.NewResultSerializer ?? sr.GetSerializer<TNewResult>();
284+
var fromSerializer = options?.FromSerializer ?? sr.GetSerializer<TFrom>();
285+
var asSerializer = options?.AsSerializer ?? sr.GetSerializer<TAs>();
286+
var renderedConnectToField = connectToField.Render(fromSerializer, sr);
287+
var renderedStartWith = startWith.Render(resultSerializer, sr);
288+
var renderedConnectFromField = connectFromField.Render(fromSerializer, sr);
289+
var renderedAs = @as.Render(newResultSerializer, sr);
290+
var renderedDepthField = depthField?.Render(asSerializer, sr);
291+
var renderedRestrictSearchWithMatch = options?.RestrictSearchWithMatch?.Render(fromSerializer, sr);
292+
var document = new BsonDocument
293+
{
294+
{ operatorName, new BsonDocument
295+
{
296+
{ "from", from.CollectionNamespace.CollectionName },
297+
{ "connectFromField", renderedConnectFromField.FieldName },
298+
{ "connectToField", renderedConnectToField.FieldName },
299+
{ "startWith", renderedStartWith },
300+
{ "as", renderedAs.FieldName },
301+
{ "depthField", () => renderedDepthField.FieldName, renderedDepthField != null },
302+
{ "maxDepth", () => options.MaxDepth.Value, options != null && options.MaxDepth.HasValue },
303+
{ "restrictSearchWithMatch", renderedRestrictSearchWithMatch, renderedRestrictSearchWithMatch != null }
304+
}
305+
}
306+
};
307+
return new RenderedPipelineStageDefinition<TNewResult>(operatorName, document, newResultSerializer);
308+
});
309+
310+
return AppendStage<TNewResult>(stage);
311+
}
312+
258313
public override IAggregateFluent<TNewResult> Group<TNewResult>(ProjectionDefinition<TResult, TNewResult> group)
259314
{
260315
const string operatorName = "$group";
@@ -479,5 +534,22 @@ public override string ToString()
479534
sb.Append("])");
480535
return sb.ToString();
481536
}
537+
538+
// private methods
539+
private bool IsTConnectOrEnumerableTConnect<TConnectFrom, TConnect>()
540+
{
541+
if (typeof(TConnect) == typeof(TConnectFrom))
542+
{
543+
return true;
544+
}
545+
546+
var ienumerableTConnect = typeof(IEnumerable<>).MakeGenericType(typeof(TConnect));
547+
if (typeof(TConnectFrom).GetTypeInfo().GetInterfaces().Contains(ienumerableTConnect))
548+
{
549+
return true;
550+
}
551+
552+
return false;
553+
}
482554
}
483555
}

src/MongoDB.Driver/AggregateFluentBase.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,20 @@ public virtual IAggregateFluent<TNewResult> Facet<TNewResult>(
9292
throw new NotImplementedException();
9393
}
9494

95+
/// <inheritdoc />
96+
public virtual IAggregateFluent<TNewResult> GraphLookup<TNewResult, TFrom, TConnect, TConnectFrom, TStartWith, TAs, TAsEnumerable>(
97+
IMongoCollection<TFrom> from,
98+
FieldDefinition<TFrom, TConnectFrom> connectFromField,
99+
FieldDefinition<TFrom, TConnect> connectToField,
100+
AggregateExpressionDefinition<TResult, TStartWith> startWith,
101+
FieldDefinition<TNewResult, TAsEnumerable> @as,
102+
FieldDefinition<TAs, int> depthField,
103+
AggregateGraphLookupOptions<TNewResult, TFrom, TConnect, TConnectFrom, TStartWith, TAs, TAsEnumerable> options = null)
104+
where TAsEnumerable : IEnumerable<TAs>
105+
{
106+
throw new NotImplementedException();
107+
}
108+
95109
/// <inheritdoc />
96110
public abstract IAggregateFluent<TNewResult> Group<TNewResult>(ProjectionDefinition<TResult, TNewResult> group);
97111

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* Copyright 2016 MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System.Collections.Generic;
17+
using MongoDB.Bson.Serialization;
18+
19+
namespace MongoDB.Driver
20+
{
21+
/// <summary>
22+
/// Represents options for the GraphLookup method.
23+
/// </summary>
24+
public class AggregateGraphLookupOptions<TNewResult, TFrom, TConnect, TConnectFrom, TStartWith, TAs, TAsEnumerable>
25+
where TAsEnumerable : IEnumerable<TAs>
26+
{
27+
internal AggregateGraphLookupOptions()
28+
{
29+
}
30+
31+
/// <summary>
32+
/// Gets or sets the TAs serialzier.
33+
/// </summary>
34+
public IBsonSerializer<TAs> AsSerializer { get; set; }
35+
36+
/// <summary>
37+
/// Gets or sets the TFrom serializer.
38+
/// </summary>
39+
public IBsonSerializer<TFrom> FromSerializer { get; set; }
40+
41+
/// <summary>
42+
/// Gets or sets the maximum depth.
43+
/// </summary>
44+
public int? MaxDepth { get; set; }
45+
46+
/// <summary>
47+
/// Gets or sets the TNewResult serializer.
48+
/// </summary>
49+
public IBsonSerializer<TNewResult> NewResultSerializer { get; set; }
50+
51+
/// <summary>
52+
/// Gets the filter to restrict the search with.
53+
/// </summary>
54+
public FilterDefinition<TFrom> RestrictSearchWithMatch { get; set; }
55+
}
56+
}

src/MongoDB.Driver/IAggregateFluent.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,34 @@ IAggregateFluent<TNewResult> Facet<TNewResult>(
135135
IEnumerable<AggregateFacet<TResult>> facets,
136136
AggregateFacetOptions<TNewResult> options = null);
137137

138+
/// <summary>
139+
/// Appends a $graphLookup stage to the pipeline.
140+
/// </summary>
141+
/// <typeparam name="TNewResult">The type of the new result (must be same as TResult with an additional as field).</typeparam>
142+
/// <typeparam name="TFrom">The type of the from documents.</typeparam>
143+
/// <typeparam name="TConnect">The type of the connect field.</typeparam>
144+
/// <typeparam name="TConnectFrom">The type of the connect from field (must be either TConnect or a type that implements IEnumerable{TConnect}).</typeparam>
145+
/// <typeparam name="TStartWith">The type of the start with expression (must be either TConnect or a type that implements IEnumerable{TConnect}).</typeparam>
146+
/// <typeparam name="TAs">The type of the documents in the as field.</typeparam>
147+
/// <typeparam name="TAsEnumerable">The type of the enumerable as field.</typeparam>
148+
/// <param name="from">The from collection.</param>
149+
/// <param name="connectFromField">The connect from field.</param>
150+
/// <param name="connectToField">The connect to field.</param>
151+
/// <param name="startWith">The start with value.</param>
152+
/// <param name="as">The as field.</param>
153+
/// <param name="depthField">The depth field.</param>
154+
/// <param name="options">The options.</param>
155+
/// <returns>The fluent aggregate interface.</returns>
156+
IAggregateFluent<TNewResult> GraphLookup<TNewResult, TFrom, TConnect, TConnectFrom, TStartWith, TAs, TAsEnumerable>(
157+
IMongoCollection<TFrom> from,
158+
FieldDefinition<TFrom, TConnectFrom> connectFromField,
159+
FieldDefinition<TFrom, TConnect> connectToField,
160+
AggregateExpressionDefinition<TResult, TStartWith> startWith,
161+
FieldDefinition<TNewResult, TAsEnumerable> @as,
162+
FieldDefinition<TAs, int> depthField,
163+
AggregateGraphLookupOptions<TNewResult, TFrom, TConnect, TConnectFrom, TStartWith, TAs, TAsEnumerable> options = null)
164+
where TAsEnumerable : IEnumerable<TAs>;
165+
138166
/// <summary>
139167
/// Appends a group stage to the pipeline.
140168
/// </summary>

0 commit comments

Comments
 (0)