Skip to content

Commit 4ddebfb

Browse files
committed
CSHARP-4804: Slice projection must be rendered differently for Find and Aggregate.
1 parent e74a0a9 commit 4ddebfb

File tree

3 files changed

+321
-8
lines changed

3 files changed

+321
-8
lines changed

src/MongoDB.Driver/ProjectionDefinition.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ public virtual BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IB
8585
/// <returns>A <see cref="BsonDocument"/>.</returns>
8686
public abstract BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider);
8787

88+
internal virtual BsonDocument RenderForFind(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
89+
=> Render(sourceSerializer, serializerRegistry, linqProvider);
90+
8891
/// <summary>
8992
/// Performs an implicit conversion from <see cref="BsonDocument"/> to <see cref="ProjectionDefinition{TSource}"/>.
9093
/// </summary>
@@ -489,7 +492,20 @@ public IBsonSerializer<TProjection> ResultSerializer
489492

490493
public override RenderedProjectionDefinition<TProjection> Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
491494
{
492-
var document = _projection.Render(sourceSerializer, serializerRegistry, linqProvider);
495+
return Render(sourceSerializer, serializerRegistry, projection => projection.Render(sourceSerializer, serializerRegistry, linqProvider));
496+
}
497+
498+
internal override RenderedProjectionDefinition<TProjection> RenderForFind(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
499+
{
500+
return Render(sourceSerializer, serializerRegistry, projection => projection.RenderForFind(sourceSerializer, serializerRegistry, linqProvider));
501+
}
502+
503+
private RenderedProjectionDefinition<TProjection> Render(
504+
IBsonSerializer<TSource> sourceSerializer,
505+
IBsonSerializerRegistry serializerRegistry,
506+
Func<ProjectionDefinition<TSource>, BsonDocument> renderer)
507+
{
508+
var document = renderer(_projection);
493509
return new RenderedProjectionDefinition<TProjection>(
494510
document,
495511
_projectionSerializer ?? (sourceSerializer as IBsonSerializer<TProjection>) ?? serializerRegistry.GetSerializer<TProjection>());

src/MongoDB.Driver/ProjectionDefinitionBuilder.cs

Lines changed: 123 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,22 @@ public static ProjectionDefinition<TDocument> SearchMeta<TDocument>(
256256
return builder.Combine(projection, builder.SearchMeta(field));
257257
}
258258

259+
/// <summary>
260+
/// Combines an existing projection with an array slice projection.
261+
/// </summary>
262+
/// <typeparam name="TDocument">The type of the document.</typeparam>
263+
/// <param name="projection">The projection.</param>
264+
/// <param name="field">The field.</param>
265+
/// <param name="limit">The limit.</param>
266+
/// <returns>
267+
/// A combined projection.
268+
/// </returns>
269+
public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, FieldDefinition<TDocument> field, int limit)
270+
{
271+
var builder = Builders<TDocument>.Projection;
272+
return builder.Combine(projection, builder.Slice(field, limit));
273+
}
274+
259275
/// <summary>
260276
/// Combines an existing projection with an array slice projection.
261277
/// </summary>
@@ -267,12 +283,28 @@ public static ProjectionDefinition<TDocument> SearchMeta<TDocument>(
267283
/// <returns>
268284
/// A combined projection.
269285
/// </returns>
270-
public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, FieldDefinition<TDocument> field, int skip, int? limit = null)
286+
public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, FieldDefinition<TDocument> field, int skip, int limit)
271287
{
272288
var builder = Builders<TDocument>.Projection;
273289
return builder.Combine(projection, builder.Slice(field, skip, limit));
274290
}
275291

292+
/// <summary>
293+
/// Combines an existing projection with an array slice projection.
294+
/// </summary>
295+
/// <typeparam name="TDocument">The type of the document.</typeparam>
296+
/// <param name="projection">The projection.</param>
297+
/// <param name="field">The field.</param>
298+
/// <param name="limit">The limit.</param>
299+
/// <returns>
300+
/// A combined projection.
301+
/// </returns>
302+
public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, Expression<Func<TDocument, object>> field, int limit)
303+
{
304+
var builder = Builders<TDocument>.Projection;
305+
return builder.Combine(projection, builder.Slice(field, limit));
306+
}
307+
276308
/// <summary>
277309
/// Combines an existing projection with an array slice projection.
278310
/// </summary>
@@ -284,7 +316,7 @@ public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDe
284316
/// <returns>
285317
/// A combined projection.
286318
/// </returns>
287-
public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, Expression<Func<TDocument, object>> field, int skip, int? limit = null)
319+
public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, Expression<Func<TDocument, object>> field, int skip, int limit)
288320
{
289321
var builder = Builders<TDocument>.Projection;
290322
return builder.Combine(projection, builder.Slice(field, skip, limit));
@@ -520,6 +552,19 @@ public ProjectionDefinition<TSource> SearchMeta(Expression<Func<TSource, object>
520552
return SearchMeta(new ExpressionFieldDefinition<TSource>(field));
521553
}
522554

555+
/// <summary>
556+
/// Creates an array slice projection.
557+
/// </summary>
558+
/// <param name="field">The field.</param>
559+
/// <param name="limit">The limit.</param>
560+
/// <returns>
561+
/// An array slice projection.
562+
/// </returns>
563+
public ProjectionDefinition<TSource> Slice(FieldDefinition<TSource> field, int limit)
564+
{
565+
return new SliceProjectionDefinition<TSource>(field, limit);
566+
}
567+
523568
/// <summary>
524569
/// Creates an array slice projection.
525570
/// </summary>
@@ -529,10 +574,22 @@ public ProjectionDefinition<TSource> SearchMeta(Expression<Func<TSource, object>
529574
/// <returns>
530575
/// An array slice projection.
531576
/// </returns>
532-
public ProjectionDefinition<TSource> Slice(FieldDefinition<TSource> field, int skip, int? limit = null)
577+
public ProjectionDefinition<TSource> Slice(FieldDefinition<TSource> field, int skip, int limit)
533578
{
534-
var value = limit.HasValue ? (BsonValue)new BsonArray { skip, limit.Value } : skip;
535-
return new SingleFieldProjectionDefinition<TSource>(field, new BsonDocument("$slice", value));
579+
return new SliceProjectionDefinition<TSource>(field, skip, limit);
580+
}
581+
582+
/// <summary>
583+
/// Creates an array slice projection.
584+
/// </summary>
585+
/// <param name="field">The field.</param>
586+
/// <param name="limit">The limit.</param>
587+
/// <returns>
588+
/// An array slice projection.
589+
/// </returns>
590+
public ProjectionDefinition<TSource> Slice(Expression<Func<TSource, object>> field, int limit)
591+
{
592+
return Slice(new ExpressionFieldDefinition<TSource>(field), limit);
536593
}
537594

538595
/// <summary>
@@ -544,7 +601,7 @@ public ProjectionDefinition<TSource> Slice(FieldDefinition<TSource> field, int s
544601
/// <returns>
545602
/// An array slice projection.
546603
/// </returns>
547-
public ProjectionDefinition<TSource> Slice(Expression<Func<TSource, object>> field, int skip, int? limit = null)
604+
public ProjectionDefinition<TSource> Slice(Expression<Func<TSource, object>> field, int skip, int limit)
548605
{
549606
return Slice(new ExpressionFieldDefinition<TSource>(field), skip, limit);
550607
}
@@ -560,12 +617,22 @@ public CombinedProjectionDefinition(IEnumerable<ProjectionDefinition<TSource>> p
560617
}
561618

562619
public override BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
620+
{
621+
return Render(projection => projection.Render(sourceSerializer, serializerRegistry, linqProvider));
622+
}
623+
624+
internal override BsonDocument RenderForFind(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
625+
{
626+
return Render(projection => projection.RenderForFind(sourceSerializer, serializerRegistry, linqProvider));
627+
}
628+
629+
private BsonDocument Render(Func<ProjectionDefinition<TSource>, BsonDocument> renderer)
563630
{
564631
var document = new BsonDocument();
565632

566633
foreach (var projection in _projections)
567634
{
568-
var renderedProjection = projection.Render(sourceSerializer, serializerRegistry, linqProvider);
635+
var renderedProjection = renderer(projection);
569636

570637
foreach (var element in renderedProjection.Elements)
571638
{
@@ -650,4 +717,53 @@ public override BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, I
650717
return new BsonDocument(renderedField.FieldName, _value);
651718
}
652719
}
720+
721+
internal sealed class SliceProjectionDefinition<TSource> : ProjectionDefinition<TSource>
722+
{
723+
private readonly FieldDefinition<TSource> _field;
724+
private readonly BsonValue _limit;
725+
private readonly BsonValue _skip;
726+
727+
public SliceProjectionDefinition(FieldDefinition<TSource> field, BsonValue limit)
728+
{
729+
_field = Ensure.IsNotNull(field, nameof(field));
730+
_limit = Ensure.IsNotNull(limit, nameof(limit));
731+
}
732+
733+
public SliceProjectionDefinition(FieldDefinition<TSource> field, BsonValue skip, BsonValue limit)
734+
{
735+
_field = Ensure.IsNotNull(field, nameof(field));
736+
_skip = skip; // can be null
737+
_limit = Ensure.IsNotNull(limit, nameof(limit));
738+
}
739+
740+
public override BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
741+
{
742+
return Render(sourceSerializer, serializerRegistry, linqProvider, RenderArgs);
743+
}
744+
745+
internal override BsonDocument RenderForFind(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
746+
{
747+
return Render(sourceSerializer, serializerRegistry, linqProvider, RenderArgsForFind);
748+
}
749+
750+
private BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider, Func<string, BsonValue> argsRenderer)
751+
{
752+
var renderedField = _field.Render(sourceSerializer, serializerRegistry, linqProvider);
753+
var sliceArgs = argsRenderer(renderedField.FieldName);
754+
return new BsonDocument(renderedField.FieldName, new BsonDocument("$slice", sliceArgs));
755+
}
756+
757+
private BsonValue RenderArgs(string fieldName)
758+
{
759+
return _skip == null ?
760+
new BsonArray { "$" + fieldName, _limit } :
761+
new BsonArray { "$" + fieldName, _skip, _limit };
762+
}
763+
764+
private BsonValue RenderArgsForFind(string fieldName)
765+
{
766+
return _skip == null ? _limit : new BsonArray { _skip, _limit };
767+
}
768+
}
653769
}

0 commit comments

Comments
 (0)