Skip to content

Commit 7f1b6ab

Browse files
committed
CSHARP-1400: added $arrayElemAt expression support.
1 parent bd0383d commit 7f1b6ab

28 files changed

+624
-169
lines changed

Docs/reference/content/reference/driver/expressions.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,21 @@ See the [MongoDB documentation]({{< docsref "meta/aggregation-quick-reference/#t
745745

746746
See the [MongoDB documentation]({{< docsref "meta/aggregation-quick-reference/#array-expressions" >}}) for more information on each operator.
747747

748+
#### $arrayElemAt
749+
750+
```csharp
751+
p => p.FavoriteNumbers.First()
752+
```
753+
```json
754+
{ $arrayElemAt: ['$FavoriteNumbers', 0] }
755+
```
756+
```csharp
757+
p => p.FavoriteNumbers.Last()
758+
```
759+
```json
760+
{ $arrayElemAt: ['$FavoriteNumbers', -1] }
761+
```
762+
748763
#### $avg
749764

750765
```csharp

src/MongoDB.Driver.Tests/Linq/MongoQueryableTests.cs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -464,9 +464,9 @@ public void OfType_with_a_field()
464464

465465
Assert(query,
466466
1,
467-
"{ $project: { 'C.E': 1, _id: 0 } }",
468-
"{ $match: { 'C.E._t': 'V' } }",
469-
"{ $match: { 'C.E.W': 1111 } }");
467+
"{ $project: { E: '$C.E', _id: 0 } }",
468+
"{ $match: { 'E._t': 'V' } }",
469+
"{ $match: { 'E.W': 1111 } }");
470470
}
471471

472472
[Test]
@@ -643,7 +643,7 @@ public void Select_scalar_where_method()
643643

644644
Assert(query,
645645
1,
646-
"{ $project: { A: 1, _id: 0 } }",
646+
"{ $project: { A: '$A', _id: 0 } }",
647647
"{ $match: { A: 'Awesome' } }");
648648
}
649649

@@ -675,7 +675,7 @@ public void Select_method_scalar()
675675

676676
Assert(query,
677677
2,
678-
"{ $project: { A: 1, _id: 0 } }");
678+
"{ $project: { A: '$A', _id: 0 } }");
679679
}
680680

681681
[Test]
@@ -686,7 +686,7 @@ public void Select_syntax_scalar()
686686

687687
Assert(query,
688688
2,
689-
"{ $project: { A: 1, _id: 0 } }");
689+
"{ $project: { A: '$A', _id: 0 } }");
690690
}
691691

692692
[Test]
@@ -717,7 +717,7 @@ public void Select_method_array()
717717

718718
Assert(query,
719719
2,
720-
"{ $project: { M: 1, _id: 0 } }");
720+
"{ $project: { M: '$M', _id: 0 } }");
721721
}
722722

723723
[Test]
@@ -728,7 +728,38 @@ public void Select_syntax_array()
728728

729729
Assert(query,
730730
2,
731-
"{ $project: { M: 1, _id: 0 } }");
731+
"{ $project: { M: '$M', _id: 0 } }");
732+
}
733+
734+
[Test]
735+
public void Select_method_array_index()
736+
{
737+
var query = CreateQuery().Select(x => x.M[0]);
738+
739+
Assert(query,
740+
2,
741+
"{ $project: { __fld0: { $arrayElemAt: ['$M', 0] }, _id: 0 } }");
742+
}
743+
744+
[Test]
745+
public void Select_syntax_array_index()
746+
{
747+
var query = from x in CreateQuery()
748+
select x.M[0];
749+
750+
Assert(query,
751+
2,
752+
"{ $project: { __fld0: { $arrayElemAt: ['$M', 0] }, _id: 0 } }");
753+
}
754+
755+
[Test]
756+
public void Select_method_embedded_pipeline()
757+
{
758+
var query = CreateQuery().Select(x => x.M.First());
759+
760+
Assert(query,
761+
2,
762+
"{ $project: { __fld0: { $arrayElemAt: ['$M', 0] }, _id: 0 } }");
732763
}
733764

734765
[Test]
@@ -764,7 +795,7 @@ public void SelectMany_with_only_resultSelector()
764795
Assert(query,
765796
4,
766797
"{ $unwind: '$G' }",
767-
"{ $project: { G: 1, _id: 0 } }");
798+
"{ $project: { G: '$G', _id: 0 } }");
768799
}
769800

770801
[Test]
@@ -776,7 +807,7 @@ public void SelectMany_with_collection_selector_method_simple_scalar()
776807
Assert(query,
777808
4,
778809
"{ $unwind: '$G' }",
779-
"{ $project: { G: 1, _id: 0 } }");
810+
"{ $project: { G: '$G', _id: 0 } }");
780811
}
781812

782813
[Test]
@@ -789,7 +820,7 @@ from y in x.G
789820
Assert(query,
790821
4,
791822
"{ $unwind: '$G' }",
792-
"{ $project: { G: 1, _id: 0 } }");
823+
"{ $project: { G: '$G', _id: 0 } }");
793824
}
794825

795826
[Test]

src/MongoDB.Driver.Tests/Linq/Translators/AggregateProjectTranslatorTests.cs

Lines changed: 140 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,138 @@ public async Task Should_translate_and_flattened()
160160
result.Value.Result.Should().BeFalse();
161161
}
162162

163+
[Test]
164+
[RequiresServer(MinimumVersion = "3.1.7")]
165+
public async Task Should_translate_arrayElemAt_using_a_constant_ElementAt()
166+
{
167+
var result = await Project(x => new { Result = x.M.ElementAt(1) });
168+
169+
result.Projection.Should().Be("{ Result: { $arrayElemAt: [\"$M\", 1] }, _id: 0 }");
170+
171+
result.Value.Result.Should().Be(4);
172+
}
173+
174+
[Test]
175+
[RequiresServer(MinimumVersion = "3.1.7")]
176+
public async Task Should_translate_arrayElemAt_using_a_constant_indexer()
177+
{
178+
var result = await Project(x => new { Result = x.M[1] });
179+
180+
result.Projection.Should().Be("{ Result: { $arrayElemAt: [\"$M\", 1] }, _id: 0 }");
181+
182+
result.Value.Result.Should().Be(4);
183+
}
184+
185+
[Test]
186+
[RequiresServer(MinimumVersion = "3.1.7")]
187+
public async Task Should_translate_arrayElemAt_using_a_constant_get_Item()
188+
{
189+
var result = await Project(x => new { Result = x.O[1] });
190+
191+
result.Projection.Should().Be("{ Result: { $arrayElemAt: [\"$O\", 1] }, _id: 0 }");
192+
193+
result.Value.Result.Should().Be(20);
194+
}
195+
196+
[Test]
197+
[RequiresServer(MinimumVersion = "3.1.7")]
198+
public async Task Should_translate_arrayElemAt_using_a_variable_ElementAt()
199+
{
200+
var result = await Project(x => new { Result = (int?)x.M.ElementAt(x.T["one"]) });
201+
202+
result.Projection.Should().Be("{ Result: { $arrayElemAt: [\"$M\", \"$T.one\"] }, _id: 0 }");
203+
204+
result.Value.Result.Should().Be(4);
205+
}
206+
207+
[Test]
208+
[RequiresServer(MinimumVersion = "3.1.7")]
209+
public async Task Should_translate_arrayElemAt_using_a_variable_indexer()
210+
{
211+
var result = await Project(x => new { Result = (int?)x.M[x.T["one"]] });
212+
213+
result.Projection.Should().Be("{ Result: { $arrayElemAt: [\"$M\", \"$T.one\"] }, _id: 0 }");
214+
215+
result.Value.Result.Should().Be(4);
216+
}
217+
218+
[Test]
219+
[RequiresServer(MinimumVersion = "3.1.7")]
220+
public async Task Should_translate_arrayElemAt_using_a_variable_get_Item()
221+
{
222+
var result = await Project(x => new { Result = (long?)x.O[x.T["one"]] });
223+
224+
result.Projection.Should().Be("{ Result: { $arrayElemAt: [\"$O\", \"$T.one\"] }, _id: 0 }");
225+
226+
result.Value.Result.Should().Be(20);
227+
}
228+
229+
[Test]
230+
[RequiresServer(MinimumVersion = "3.1.7")]
231+
public async Task Should_translate_arrayElemAt_using_a_constant_ElementAt_followed_by_a_field()
232+
{
233+
var result = await Project(x => new { Result = x.G.ElementAt(1).D });
234+
235+
result.Projection.Should().Be("{ Result: { $let: { vars: { item: { \"$arrayElemAt\": [\"$G\", 1] } }, in: \"$$item.D\" } }, _id: 0 }");
236+
237+
result.Value.Result.Should().Be("Dolphin");
238+
}
239+
240+
[Test]
241+
[RequiresServer(MinimumVersion = "3.1.7")]
242+
public async Task Should_translate_arrayElemAt_using_a_variable_ElementAt_followed_by_a_field()
243+
{
244+
var result = await Project(x => new { Result = x.G.ElementAt(x.T["one"]).D });
245+
246+
result.Projection.Should().Be("{ Result: { $let: { vars: { item: { \"$arrayElemAt\": [\"$G\", \"$T.one\"] } }, in: \"$$item.D\" } }, _id: 0 }");
247+
248+
result.Value.Result.Should().Be("Dolphin");
249+
}
250+
251+
[Test]
252+
[RequiresServer(MinimumVersion = "3.1.7")]
253+
public async Task Should_translate_arrayElemAt_using_First()
254+
{
255+
var result = await Project(x => new { Result = x.M.First() });
256+
257+
result.Projection.Should().Be("{ Result: { \"$arrayElemAt\": [\"$M\", 0] }, _id: 0 }");
258+
259+
result.Value.Result.Should().Be(2);
260+
}
261+
262+
[Test]
263+
[RequiresServer(MinimumVersion = "3.1.7")]
264+
public async Task Should_translate_arrayElemAt_using_First_followed_by_a_field()
265+
{
266+
var result = await Project(x => new { Result = x.G.First().D });
267+
268+
result.Projection.Should().Be("{ Result: { $arrayElemAt: [\"$G.D\", 0] }, _id: 0 }");
269+
270+
result.Value.Result.Should().Be("Don't");
271+
}
272+
273+
[Test]
274+
[RequiresServer(MinimumVersion = "3.1.7")]
275+
public async Task Should_translate_arrayElemAt_using_Last()
276+
{
277+
var result = await Project(x => new { Result = x.M.Last() });
278+
279+
result.Projection.Should().Be("{ Result: { \"$arrayElemAt\": [\"$M\", -1] }, _id: 0 }");
280+
281+
result.Value.Result.Should().Be(5);
282+
}
283+
284+
[Test]
285+
[RequiresServer(MinimumVersion = "3.1.7")]
286+
public async Task Should_translate_arrayElemAt_using_Last_followed_by_a_field()
287+
{
288+
var result = await Project(x => new { Result = x.G.Last().D });
289+
290+
result.Projection.Should().Be("{ Result: { \"$arrayElemAt\": [\"$G.D\", -1] }, _id: 0 }");
291+
292+
result.Value.Result.Should().Be("Dolphin");
293+
}
294+
163295
[Test]
164296
[RequiresServer(MinimumVersion = "3.1.7")]
165297
public async Task Should_translate_avg()
@@ -1139,15 +1271,15 @@ private async Task<ProjectedResult<TResult>> Project<TResult>(Expression<Func<Ro
11391271
var projectionInfo = AggregateProjectTranslator.Translate(projector, serializer, BsonSerializer.SerializerRegistry);
11401272

11411273
var pipelineOperator = new BsonDocument("$project", projectionInfo.Document);
1142-
using (var cursor = await _collection.AggregateAsync(new PipelineStagePipelineDefinition<Root, TResult>(new PipelineStageDefinition<Root, TResult>[] { pipelineOperator }, projectionInfo.ProjectionSerializer)))
1274+
var result = await _collection.Aggregate()
1275+
.Project(new BsonDocumentProjectionDefinition<Root, TResult>(projectionInfo.Document, projectionInfo.ProjectionSerializer))
1276+
.FirstAsync();
1277+
1278+
return new ProjectedResult<TResult>
11431279
{
1144-
var list = await cursor.ToListAsync();
1145-
return new ProjectedResult<TResult>
1146-
{
1147-
Projection = projectionInfo.Document,
1148-
Value = (TResult)list[0]
1149-
};
1150-
}
1280+
Projection = projectionInfo.Document,
1281+
Value = result
1282+
};
11511283
}
11521284

11531285
private class ProjectedResult<T>

src/MongoDB.Driver/FieldDefinition.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using MongoDB.Driver.Linq;
2323
using MongoDB.Driver.Linq.Expressions;
2424
using MongoDB.Driver.Linq.Processors;
25+
using MongoDB.Driver.Linq.Translators;
2526

2627
namespace MongoDB.Driver
2728
{
@@ -218,6 +219,7 @@ public override RenderedFieldDefinition Render(IBsonSerializer<TDocument> docume
218219
var parameterExpression = new DocumentExpression(documentSerializer);
219220
bindingContext.AddExpressionMapping(lambda.Parameters[0], parameterExpression);
220221
var bound = bindingContext.Bind(lambda.Body);
222+
bound = FieldExpressionFlattener.FlattenFields(bound);
221223
IFieldExpression field;
222224
if (!ExpressionHelper.TryGetExpression(bound, out field))
223225
{
@@ -263,6 +265,7 @@ public override RenderedFieldDefinition<TField> Render(IBsonSerializer<TDocument
263265
var parameterExpression = new DocumentExpression(documentSerializer);
264266
bindingContext.AddExpressionMapping(lambda.Parameters[0], parameterExpression);
265267
var bound = bindingContext.Bind(lambda.Body);
268+
bound = FieldExpressionFlattener.FlattenFields(bound);
266269
IFieldExpression field;
267270
if (!ExpressionHelper.TryGetExpression(bound, out field))
268271
{

src/MongoDB.Driver/Linq/Expressions/AccumulatorExpression.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public Expression Argument
4747
get { return _argument; }
4848
}
4949

50+
public Expression Document
51+
{
52+
get { return null; }
53+
}
54+
5055
public override ExtensionExpressionType ExtensionType
5156
{
5257
get { return ExtensionExpressionType.Accumulator; }

0 commit comments

Comments
 (0)