Skip to content

Commit cfa834f

Browse files
CSHARP-3700: Support 'let' option for aggregate command (#570)
1 parent 00de389 commit cfa834f

File tree

14 files changed

+899
-3
lines changed

14 files changed

+899
-3
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class Feature
3636
private static readonly Feature __aggregateFunction = new Feature("AggregateFunction", new SemanticVersion(4, 3, 4));
3737
private static readonly Feature __aggregateGraphLookupStage = new Feature("AggregateGraphLookupStage", new SemanticVersion(3, 4, 0, "rc0"));
3838
private static readonly Feature __aggregateHint = new Feature("AggregateHint", new SemanticVersion(3, 6, 0, "rc0"));
39+
private static readonly Feature __aggregateOptionsLet = new Feature("AggregateOptionsLet", new SemanticVersion(5, 0, 0, ""));
3940
private static readonly Feature __aggregateLet = new Feature("AggregateLet", new SemanticVersion(3, 6, 0));
4041
private static readonly Feature __aggregateMerge = new Feature("AggregateMerge", new SemanticVersion(4, 2, 0));
4142
private static readonly Feature __aggregateOut = new Feature("AggregateOut", new SemanticVersion(2, 6, 0));
@@ -175,6 +176,11 @@ public class Feature
175176
/// <summary>
176177
/// Gets the aggregate let feature.
177178
/// </summary>
179+
public static Feature AggregateOptionsLet => __aggregateOptionsLet;
180+
181+
/// <summary>
182+
/// Gets the aggregate lookup stage let feature.
183+
/// </summary>
178184
public static Feature AggregateLet => __aggregateLet;
179185

180186
/// <summary>

src/MongoDB.Driver.Core/Core/Operations/AggregateOperation.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class AggregateOperation<TResult> : IReadOperation<IAsyncCursor<TResult>>
4545
private string _comment;
4646
private readonly DatabaseNamespace _databaseNamespace;
4747
private BsonValue _hint;
48+
private BsonDocument _let;
4849
private TimeSpan? _maxAwaitTime;
4950
private TimeSpan? _maxTime;
5051
private readonly MessageEncoderSettings _messageEncoderSettings;
@@ -168,6 +169,18 @@ public BsonValue Hint
168169
set { _hint = value; }
169170
}
170171

172+
/// <summary>
173+
/// Gets or sets the "let" definition.
174+
/// </summary>
175+
/// <value>
176+
/// The "let" definition.
177+
/// </value>
178+
public BsonDocument Let
179+
{
180+
get { return _let; }
181+
set { _let = value; }
182+
}
183+
171184
/// <summary>
172185
/// Gets or sets the maximum await time.
173186
/// </summary>
@@ -346,6 +359,7 @@ internal BsonDocument CreateCommand(ConnectionDescription connectionDescription,
346359
{ "maxTimeMS", () => MaxTimeHelper.ToMaxTimeMS(_maxTime.Value), _maxTime.HasValue },
347360
{ "collation", () => _collation.ToBsonDocument(), _collation != null },
348361
{ "hint", () => _hint, _hint != null },
362+
{ "let", () => _let, _let != null },
349363
{ "comment", () => _comment, _comment != null },
350364
{ "readConcern", readConcern, readConcern != null }
351365
};

src/MongoDB.Driver.Core/Core/Operations/AggregateToCollectionOperation.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class AggregateToCollectionOperation : IWriteOperation<BsonDocument>
4141
private string _comment;
4242
private readonly DatabaseNamespace _databaseNamespace;
4343
private BsonValue _hint;
44+
private BsonDocument _let;
4445
private TimeSpan? _maxTime;
4546
private readonly MessageEncoderSettings _messageEncoderSettings;
4647
private readonly IReadOnlyList<BsonDocument> _pipeline;
@@ -159,6 +160,18 @@ public BsonValue Hint
159160
set { _hint = value; }
160161
}
161162

163+
/// <summary>
164+
/// Gets or sets the "let" definition.
165+
/// </summary>
166+
/// <value>
167+
/// The "let" definition.
168+
/// </value>
169+
public BsonDocument Let
170+
{
171+
get { return _let; }
172+
set { _let = value; }
173+
}
174+
162175
/// <summary>
163176
/// Gets or sets the maximum time the server should spend on this operation.
164177
/// </summary>
@@ -270,6 +283,7 @@ internal BsonDocument CreateCommand(ICoreSessionHandle session, ConnectionDescri
270283
{ "writeConcern", writeConcern, writeConcern != null },
271284
{ "cursor", new BsonDocument(), serverVersion >= new SemanticVersion(3, 6, 0) },
272285
{ "hint", () => _hint, _hint != null },
286+
{ "let", () => _let, _let != null },
273287
{ "comment", () => _comment, _comment != null }
274288
};
275289
}

src/MongoDB.Driver/AggregateOptions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public class AggregateOptions
3131
private Collation _collation;
3232
private string _comment;
3333
private BsonValue _hint;
34+
private BsonDocument _let;
3435
private TimeSpan? _maxAwaitTime;
3536
private TimeSpan? _maxTime;
3637
private ExpressionTranslationOptions _translationOptions;
@@ -91,6 +92,15 @@ public BsonValue Hint
9192
set { _hint = value; }
9293
}
9394

95+
/// <summary>
96+
/// Gets or sets the "let" definition.
97+
/// </summary>
98+
public BsonDocument Let
99+
{
100+
get { return _let; }
101+
set { _let = value; }
102+
}
103+
94104
/// <summary>
95105
/// Gets or sets the maximum await time.
96106
/// </summary>

src/MongoDB.Driver/MongoCollectionImpl.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,7 @@ private AggregateOperation<TResult> CreateAggregateOperation<TResult>(RenderedPi
771771
Collation = options.Collation,
772772
Comment = options.Comment,
773773
Hint = options.Hint,
774+
Let = options.Let,
774775
MaxAwaitTime = options.MaxAwaitTime,
775776
MaxTime = options.MaxTime,
776777
ReadConcern = _settings.ReadConcern,
@@ -868,6 +869,7 @@ private AggregateToCollectionOperation CreateAggregateToCollectionOperation<TRes
868869
Collation = options.Collation,
869870
Comment = options.Comment,
870871
Hint = options.Hint,
872+
Let = options.Let,
871873
MaxTime = options.MaxTime,
872874
ReadConcern = _settings.ReadConcern,
873875
WriteConcern = _settings.WriteConcern

src/MongoDB.Driver/MongoDatabaseImpl.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ private AggregateOperation<TResult> CreateAggregateOperation<TResult>(RenderedPi
520520
Collation = options.Collation,
521521
Comment = options.Comment,
522522
Hint = options.Hint,
523+
Let = options.Let,
523524
MaxAwaitTime = options.MaxAwaitTime,
524525
MaxTime = options.MaxTime,
525526
ReadConcern = _settings.ReadConcern,
@@ -605,6 +606,7 @@ private AggregateToCollectionOperation CreateAggregateToCollectionOperation<TRes
605606
Collation = options.Collation,
606607
Comment = options.Comment,
607608
Hint = options.Hint,
609+
Let = options.Let,
608610
MaxTime = options.MaxTime,
609611
ReadConcern = _settings.ReadConcern,
610612
WriteConcern = _settings.WriteConcern

tests/MongoDB.Driver.Core.Tests/Core/Operations/AggregateOperationTests.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,18 @@ public void Hint_get_and_set_should_work()
210210
result.Should().BeSameAs(value);
211211
}
212212

213+
[Fact]
214+
public void Let_get_and_set_should_work()
215+
{
216+
var subject = new AggregateOperation<BsonDocument>(_collectionNamespace, __pipeline, __resultSerializer, _messageEncoderSettings);
217+
var value = new BsonDocument("y", "z");
218+
219+
subject.Let = value;
220+
var result = subject.Let;
221+
222+
result.Should().BeSameAs(value);
223+
}
224+
213225
[Fact]
214226
public void MaxAwaitTime_get_and_set_should_work()
215227
{
@@ -460,6 +472,33 @@ public void CreateCommand_should_return_the_expected_result_when_Hint_is_set(
460472
result.Should().Be(expectedResult);
461473
}
462474

475+
[Theory]
476+
[ParameterAttributeData]
477+
public void CreateCommand_should_return_expected_result_when_Let_is_set(
478+
[Values(null, "{ y : 'z' }")]
479+
string letJson)
480+
{
481+
var let = letJson == null ? null : BsonDocument.Parse(letJson);
482+
var subject = new AggregateOperation<BsonDocument>(_collectionNamespace, __pipeline, __resultSerializer, _messageEncoderSettings)
483+
{
484+
Let = let
485+
};
486+
487+
var connectionDescription = OperationTestHelper.CreateConnectionDescription(Feature.AggregateOptionsLet.FirstSupportedVersion);
488+
var session = OperationTestHelper.CreateSession();
489+
490+
var result = subject.CreateCommand(connectionDescription, session);
491+
492+
var expectedResult = new BsonDocument
493+
{
494+
{ "aggregate", _collectionNamespace.CollectionName },
495+
{ "pipeline", new BsonArray(__pipeline) },
496+
{ "let", () => let, let != null },
497+
{ "cursor", new BsonDocument() }
498+
};
499+
result.Should().Be(expectedResult);
500+
}
501+
463502
[Theory]
464503
[InlineData(-10000, 0)]
465504
[InlineData(0, 0)]
@@ -798,6 +837,53 @@ public void Execute_should_return_expected_result_when_Hint_is_set(
798837
result.Should().NotBeNull();
799838
}
800839

840+
[SkippableTheory]
841+
[ParameterAttributeData]
842+
public void Execute_should_return_expected_result_when_Let_is_set_with_match_expression(
843+
[Values(false, true)]
844+
bool async)
845+
{
846+
RequireServer.Check().Supports(Feature.AggregateOptionsLet);
847+
EnsureTestData();
848+
var pipeline = new[] { BsonDocument.Parse("{ $match : { $expr : { $eq : [ '$x', '$$y'] } } }") };
849+
var subject = new AggregateOperation<BsonDocument>(_collectionNamespace, pipeline, __resultSerializer, _messageEncoderSettings)
850+
{
851+
Let = new BsonDocument("y", "x")
852+
};
853+
854+
var cursor = ExecuteOperation(subject, async);
855+
var result = ReadCursorToEnd(cursor, async);
856+
857+
result.Should().BeEquivalentTo(new[]
858+
{
859+
new BsonDocument { { "_id", 1 }, { "x", "x" } }
860+
});
861+
}
862+
863+
[SkippableTheory]
864+
[ParameterAttributeData]
865+
public void Execute_should_return_expected_result_when_Let_is_set_with_project(
866+
[Values(false, true)]
867+
bool async)
868+
{
869+
RequireServer.Check().Supports(Feature.AggregateOptionsLet);
870+
EnsureTestData();
871+
var pipeline = new[] { BsonDocument.Parse("{ $project : { y : '$$z' } }") };
872+
var subject = new AggregateOperation<BsonDocument>(_collectionNamespace, pipeline, __resultSerializer, _messageEncoderSettings)
873+
{
874+
Let = new BsonDocument("z", "x")
875+
};
876+
877+
var cursor = ExecuteOperation(subject, async);
878+
var result = ReadCursorToEnd(cursor, async);
879+
880+
result.Should().BeEquivalentTo(new[]
881+
{
882+
new BsonDocument { { "_id", 1 }, { "y", "x" } },
883+
new BsonDocument { { "_id", 2 }, { "y", "x" } }
884+
});
885+
}
886+
801887
[SkippableTheory]
802888
[ParameterAttributeData]
803889
public void Execute_should_return_expected_result_when_MaxAwaitTime_is_set(

tests/MongoDB.Driver.Core.Tests/Core/Operations/AggregateToCollectionOperationTests.cs

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
using System.Linq;
1919
using FluentAssertions;
2020
using MongoDB.Bson;
21-
using MongoDB.Bson.Serialization;
2221
using MongoDB.Bson.TestHelpers.XunitExtensions;
2322
using MongoDB.Driver.Core.Clusters;
2423
using MongoDB.Driver.Core.Misc;
@@ -200,6 +199,18 @@ public void Hint_get_and_set_should_work()
200199
result.Should().BeSameAs(value);
201200
}
202201

202+
[Fact]
203+
public void Let_get_and_set_should_work()
204+
{
205+
var subject = new AggregateToCollectionOperation(_collectionNamespace, __pipeline, _messageEncoderSettings);
206+
var value = new BsonDocument("x", "y");
207+
208+
subject.Let = value;
209+
var result = subject.Let;
210+
211+
result.Should().BeSameAs(value);
212+
}
213+
203214
[Theory]
204215
[ParameterAttributeData]
205216
public void MaxTime_get_and_set_should_work(
@@ -419,6 +430,32 @@ public void CreateCommand_should_return_the_expected_result_when_Hint_is_set(
419430
result.Should().Be(expectedResult);
420431
}
421432

433+
[Theory]
434+
[ParameterAttributeData]
435+
public void CreateCommand_should_return_the_expected_result_when_Let_is_set(
436+
[Values(null, "{ y : 'z' }")]
437+
string letJson)
438+
{
439+
var let = letJson == null ? null : BsonDocument.Parse(letJson);
440+
var subject = new AggregateToCollectionOperation(_collectionNamespace, __pipeline, _messageEncoderSettings)
441+
{
442+
Let = let
443+
};
444+
var session = OperationTestHelper.CreateSession();
445+
var connectionDescription = OperationTestHelper.CreateConnectionDescription();
446+
447+
var result = subject.CreateCommand(session, connectionDescription);
448+
449+
var expectedResult = new BsonDocument
450+
{
451+
{ "aggregate", _collectionNamespace.CollectionName },
452+
{ "pipeline", new BsonArray(__pipeline) },
453+
{ "cursor", new BsonDocument() },
454+
{ "let", () => let, let != null }
455+
};
456+
result.Should().Be(expectedResult);
457+
}
458+
422459
[Theory]
423460
[InlineData(-10000, 0)]
424461
[InlineData(0, 0)]
@@ -718,6 +755,61 @@ public void Execute_should_return_expected_result_when_Hint_is_set(
718755
result.Should().NotBeNull();
719756
}
720757

758+
[SkippableTheory]
759+
[ParameterAttributeData]
760+
public void Execute_should_return_expected_result_when_Let_is_set_with_match_expression(
761+
[Values(false, true)]
762+
bool async)
763+
{
764+
RequireServer.Check().Supports(Feature.AggregateOptionsLet);
765+
EnsureTestData();
766+
var pipeline = new[]
767+
{
768+
BsonDocument.Parse("{ $match : { $expr : { $eq : [ '$x', '$$y'] } } }"),
769+
BsonDocument.Parse("{ $out : \"awesome\" }")
770+
};
771+
var subject = new AggregateToCollectionOperation(_collectionNamespace, pipeline, _messageEncoderSettings)
772+
{
773+
Let = new BsonDocument("y", "x")
774+
};
775+
776+
ExecuteOperation(subject, async);
777+
var result = ReadAllFromCollection(new CollectionNamespace(_databaseNamespace, "awesome"), async);
778+
779+
result.Should().BeEquivalentTo(new[]
780+
{
781+
new BsonDocument { { "_id", 1 }, { "x", "x" } }
782+
});
783+
}
784+
785+
[SkippableTheory]
786+
[ParameterAttributeData]
787+
public void Execute_should_return_expected_result_when_Let_is_set_with_project(
788+
[Values(false, true)]
789+
bool async)
790+
{
791+
RequireServer.Check().Supports(Feature.AggregateOptionsLet);
792+
EnsureTestData();
793+
var pipeline = new[]
794+
{
795+
BsonDocument.Parse("{ $project : { y : '$$z' } }"),
796+
BsonDocument.Parse("{ $out : \"awesome\" }")
797+
};
798+
var subject = new AggregateToCollectionOperation(_collectionNamespace, pipeline, _messageEncoderSettings)
799+
{
800+
Let = new BsonDocument("z", "x")
801+
};
802+
803+
ExecuteOperation(subject, async);
804+
var result = ReadAllFromCollection(new CollectionNamespace(_databaseNamespace, "awesome"), async);
805+
806+
result.Should().BeEquivalentTo(new[]
807+
{
808+
new BsonDocument { { "_id", 1 }, { "y", "x" } },
809+
new BsonDocument { { "_id", 2 }, { "y", "x" } }
810+
});
811+
}
812+
721813
[SkippableTheory]
722814
[ParameterAttributeData]
723815
public void Execute_should_return_expected_result_when_MaxTime_is_set(

0 commit comments

Comments
 (0)