Skip to content

Commit

Permalink
CSHARP-4804: Slice projection must be rendered differently for Find a…
Browse files Browse the repository at this point in the history
…nd Aggregate.
  • Loading branch information
rstam committed Oct 6, 2023
1 parent e74a0a9 commit 4ddebfb
Show file tree
Hide file tree
Showing 3 changed files with 321 additions and 8 deletions.
18 changes: 17 additions & 1 deletion src/MongoDB.Driver/ProjectionDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ public virtual BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IB
/// <returns>A <see cref="BsonDocument"/>.</returns>
public abstract BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider);

internal virtual BsonDocument RenderForFind(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
=> Render(sourceSerializer, serializerRegistry, linqProvider);

/// <summary>
/// Performs an implicit conversion from <see cref="BsonDocument"/> to <see cref="ProjectionDefinition{TSource}"/>.
/// </summary>
Expand Down Expand Up @@ -489,7 +492,20 @@ public IBsonSerializer<TProjection> ResultSerializer

public override RenderedProjectionDefinition<TProjection> Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
{
var document = _projection.Render(sourceSerializer, serializerRegistry, linqProvider);
return Render(sourceSerializer, serializerRegistry, projection => projection.Render(sourceSerializer, serializerRegistry, linqProvider));
}

internal override RenderedProjectionDefinition<TProjection> RenderForFind(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
{
return Render(sourceSerializer, serializerRegistry, projection => projection.RenderForFind(sourceSerializer, serializerRegistry, linqProvider));
}

private RenderedProjectionDefinition<TProjection> Render(
IBsonSerializer<TSource> sourceSerializer,
IBsonSerializerRegistry serializerRegistry,
Func<ProjectionDefinition<TSource>, BsonDocument> renderer)
{
var document = renderer(_projection);
return new RenderedProjectionDefinition<TProjection>(
document,
_projectionSerializer ?? (sourceSerializer as IBsonSerializer<TProjection>) ?? serializerRegistry.GetSerializer<TProjection>());
Expand Down
130 changes: 123 additions & 7 deletions src/MongoDB.Driver/ProjectionDefinitionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,22 @@ public static ProjectionDefinition<TDocument> SearchMeta<TDocument>(
return builder.Combine(projection, builder.SearchMeta(field));
}

/// <summary>
/// Combines an existing projection with an array slice projection.
/// </summary>
/// <typeparam name="TDocument">The type of the document.</typeparam>
/// <param name="projection">The projection.</param>
/// <param name="field">The field.</param>
/// <param name="limit">The limit.</param>
/// <returns>
/// A combined projection.
/// </returns>
public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, FieldDefinition<TDocument> field, int limit)
{
var builder = Builders<TDocument>.Projection;
return builder.Combine(projection, builder.Slice(field, limit));
}

/// <summary>
/// Combines an existing projection with an array slice projection.
/// </summary>
Expand All @@ -267,12 +283,28 @@ public static ProjectionDefinition<TDocument> SearchMeta<TDocument>(
/// <returns>
/// A combined projection.
/// </returns>
public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, FieldDefinition<TDocument> field, int skip, int? limit = null)
public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, FieldDefinition<TDocument> field, int skip, int limit)
{
var builder = Builders<TDocument>.Projection;
return builder.Combine(projection, builder.Slice(field, skip, limit));
}

/// <summary>
/// Combines an existing projection with an array slice projection.
/// </summary>
/// <typeparam name="TDocument">The type of the document.</typeparam>
/// <param name="projection">The projection.</param>
/// <param name="field">The field.</param>
/// <param name="limit">The limit.</param>
/// <returns>
/// A combined projection.
/// </returns>
public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, Expression<Func<TDocument, object>> field, int limit)
{
var builder = Builders<TDocument>.Projection;
return builder.Combine(projection, builder.Slice(field, limit));
}

/// <summary>
/// Combines an existing projection with an array slice projection.
/// </summary>
Expand All @@ -284,7 +316,7 @@ public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDe
/// <returns>
/// A combined projection.
/// </returns>
public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, Expression<Func<TDocument, object>> field, int skip, int? limit = null)
public static ProjectionDefinition<TDocument> Slice<TDocument>(this ProjectionDefinition<TDocument> projection, Expression<Func<TDocument, object>> field, int skip, int limit)
{
var builder = Builders<TDocument>.Projection;
return builder.Combine(projection, builder.Slice(field, skip, limit));
Expand Down Expand Up @@ -520,6 +552,19 @@ public ProjectionDefinition<TSource> SearchMeta(Expression<Func<TSource, object>
return SearchMeta(new ExpressionFieldDefinition<TSource>(field));
}

/// <summary>
/// Creates an array slice projection.
/// </summary>
/// <param name="field">The field.</param>
/// <param name="limit">The limit.</param>
/// <returns>
/// An array slice projection.
/// </returns>
public ProjectionDefinition<TSource> Slice(FieldDefinition<TSource> field, int limit)
{
return new SliceProjectionDefinition<TSource>(field, limit);
}

/// <summary>
/// Creates an array slice projection.
/// </summary>
Expand All @@ -529,10 +574,22 @@ public ProjectionDefinition<TSource> SearchMeta(Expression<Func<TSource, object>
/// <returns>
/// An array slice projection.
/// </returns>
public ProjectionDefinition<TSource> Slice(FieldDefinition<TSource> field, int skip, int? limit = null)
public ProjectionDefinition<TSource> Slice(FieldDefinition<TSource> field, int skip, int limit)
{
var value = limit.HasValue ? (BsonValue)new BsonArray { skip, limit.Value } : skip;
return new SingleFieldProjectionDefinition<TSource>(field, new BsonDocument("$slice", value));
return new SliceProjectionDefinition<TSource>(field, skip, limit);
}

/// <summary>
/// Creates an array slice projection.
/// </summary>
/// <param name="field">The field.</param>
/// <param name="limit">The limit.</param>
/// <returns>
/// An array slice projection.
/// </returns>
public ProjectionDefinition<TSource> Slice(Expression<Func<TSource, object>> field, int limit)
{
return Slice(new ExpressionFieldDefinition<TSource>(field), limit);
}

/// <summary>
Expand All @@ -544,7 +601,7 @@ public ProjectionDefinition<TSource> Slice(FieldDefinition<TSource> field, int s
/// <returns>
/// An array slice projection.
/// </returns>
public ProjectionDefinition<TSource> Slice(Expression<Func<TSource, object>> field, int skip, int? limit = null)
public ProjectionDefinition<TSource> Slice(Expression<Func<TSource, object>> field, int skip, int limit)
{
return Slice(new ExpressionFieldDefinition<TSource>(field), skip, limit);
}
Expand All @@ -560,12 +617,22 @@ public CombinedProjectionDefinition(IEnumerable<ProjectionDefinition<TSource>> p
}

public override BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
{
return Render(projection => projection.Render(sourceSerializer, serializerRegistry, linqProvider));
}

internal override BsonDocument RenderForFind(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
{
return Render(projection => projection.RenderForFind(sourceSerializer, serializerRegistry, linqProvider));
}

private BsonDocument Render(Func<ProjectionDefinition<TSource>, BsonDocument> renderer)
{
var document = new BsonDocument();

foreach (var projection in _projections)
{
var renderedProjection = projection.Render(sourceSerializer, serializerRegistry, linqProvider);
var renderedProjection = renderer(projection);

foreach (var element in renderedProjection.Elements)
{
Expand Down Expand Up @@ -650,4 +717,53 @@ public override BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, I
return new BsonDocument(renderedField.FieldName, _value);
}
}

internal sealed class SliceProjectionDefinition<TSource> : ProjectionDefinition<TSource>
{
private readonly FieldDefinition<TSource> _field;
private readonly BsonValue _limit;
private readonly BsonValue _skip;

public SliceProjectionDefinition(FieldDefinition<TSource> field, BsonValue limit)
{
_field = Ensure.IsNotNull(field, nameof(field));
_limit = Ensure.IsNotNull(limit, nameof(limit));
}

public SliceProjectionDefinition(FieldDefinition<TSource> field, BsonValue skip, BsonValue limit)
{
_field = Ensure.IsNotNull(field, nameof(field));
_skip = skip; // can be null
_limit = Ensure.IsNotNull(limit, nameof(limit));
}

public override BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
{
return Render(sourceSerializer, serializerRegistry, linqProvider, RenderArgs);
}

internal override BsonDocument RenderForFind(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
{
return Render(sourceSerializer, serializerRegistry, linqProvider, RenderArgsForFind);
}

private BsonDocument Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider, Func<string, BsonValue> argsRenderer)
{
var renderedField = _field.Render(sourceSerializer, serializerRegistry, linqProvider);
var sliceArgs = argsRenderer(renderedField.FieldName);
return new BsonDocument(renderedField.FieldName, new BsonDocument("$slice", sliceArgs));
}

private BsonValue RenderArgs(string fieldName)
{
return _skip == null ?
new BsonArray { "$" + fieldName, _limit } :
new BsonArray { "$" + fieldName, _skip, _limit };
}

private BsonValue RenderArgsForFind(string fieldName)
{
return _skip == null ? _limit : new BsonArray { _skip, _limit };
}
}
}
Loading

0 comments on commit 4ddebfb

Please sign in to comment.