diff --git a/src/MongoDB.Driver/AggregateBucketAutoResultIdSerializer.cs b/src/MongoDB.Driver/AggregateBucketAutoResultIdSerializer.cs
new file mode 100644
index 00000000000..7185dbdd45d
--- /dev/null
+++ b/src/MongoDB.Driver/AggregateBucketAutoResultIdSerializer.cs
@@ -0,0 +1,103 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using MongoDB.Bson;
+using MongoDB.Bson.IO;
+using MongoDB.Bson.Serialization;
+using MongoDB.Bson.Serialization.Serializers;
+using MongoDB.Driver.Core.Misc;
+
+namespace MongoDB.Driver
+{
+ ///
+ /// Static factory class for AggregateBucketAutoResultIdSerializer.
+ ///
+ public static class AggregateBucketAutoResultIdSerializer
+ {
+ ///
+ /// Creates an instance of AggregateBucketAutoResultIdSerializer.
+ ///
+ /// The value type.
+ /// The value serializer.
+ /// A AggregateBucketAutoResultIdSerializer.
+ public static IBsonSerializer> Create(IBsonSerializer valueSerializer)
+ {
+ return new AggregateBucketAutoResultIdSerializer(valueSerializer);
+ }
+ }
+
+ ///
+ /// A serializer for AggregateBucketAutoResultId.
+ ///
+ /// The type of the values.
+ public class AggregateBucketAutoResultIdSerializer : ClassSerializerBase>, IBsonDocumentSerializer
+ {
+ private readonly IBsonSerializer _valueSerializer;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value serializer.
+ public AggregateBucketAutoResultIdSerializer(IBsonSerializer valueSerializer)
+ {
+ _valueSerializer = Ensure.IsNotNull(valueSerializer, nameof(valueSerializer));
+ }
+
+ ///
+ protected override AggregateBucketAutoResultId DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args)
+ {
+ var reader = context.Reader;
+ reader.ReadStartDocument();
+ TValue min = default;
+ TValue max = default;
+ while (reader.ReadBsonType() != 0)
+ {
+ var name = reader.ReadName();
+ switch (name)
+ {
+ case "min": min = _valueSerializer.Deserialize(context); break;
+ case "max": max = _valueSerializer.Deserialize(context); break;
+ default: throw new BsonSerializationException($"Invalid element name for AggregateBucketAutoResultId: {name}.");
+ }
+ }
+ reader.ReadEndDocument();
+ return new AggregateBucketAutoResultId(min, max);
+ }
+
+ ///
+ protected override void SerializeValue(BsonSerializationContext context, BsonSerializationArgs args, AggregateBucketAutoResultId value)
+ {
+ var writer = context.Writer;
+ writer.WriteStartDocument();
+ writer.WriteName("min");
+ _valueSerializer.Serialize(context, value.Min);
+ writer.WriteName("max");
+ _valueSerializer.Serialize(context, value.Max);
+ writer.WriteEndDocument();
+ }
+
+ ///
+ public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializationInfo serializationInfo)
+ {
+ serializationInfo = memberName switch
+ {
+ "Min" => new BsonSerializationInfo("min", _valueSerializer, _valueSerializer.ValueType),
+ "Max" => new BsonSerializationInfo("max", _valueSerializer, _valueSerializer.ValueType),
+ _ => null
+ };
+ return serializationInfo != null;
+ }
+ }
+}
diff --git a/src/MongoDB.Driver/GroupForLinq3Result.cs b/src/MongoDB.Driver/GroupForLinq3Result.cs
deleted file mode 100644
index a5288aa96bc..00000000000
--- a/src/MongoDB.Driver/GroupForLinq3Result.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-/* Copyright 2010-present MongoDB Inc.
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-
-using System.Linq;
-
-namespace MongoDB.Driver
-{
- ///
- /// Represents the return result from PipelineDefinitionBuilder.GroupForLinq3 method.
- ///
- /// The type of the input documents.
- /// The type of the values.
- /// The type of the output documents.
- public class GroupForLinq3Result
- {
- internal GroupForLinq3Result(PipelineStageDefinition> groupStage, PipelineStageDefinition, TOutput> projectStage)
- {
- GroupStage = groupStage;
- ProjectStage = projectStage;
- }
-
- ///
- /// The resulting group stage.
- ///
- public PipelineStageDefinition> GroupStage { get; }
-
- ///
- /// The resulting project stage.
- ///
- public PipelineStageDefinition, TOutput> ProjectStage { get; }
-
- ///
- /// Deconstructs this class into its components.
- ///
- /// The group stage.
- /// The project stage.
- public void Deconstruct(
- out PipelineStageDefinition> groupStage,
- out PipelineStageDefinition, TOutput> projectStage)
- {
- groupStage = GroupStage;
- projectStage = ProjectStage;
- }
- }
-}
diff --git a/src/MongoDB.Driver/IAggregateFluentExtensions.cs b/src/MongoDB.Driver/IAggregateFluentExtensions.cs
index 702f9b65db1..6debce899fd 100644
--- a/src/MongoDB.Driver/IAggregateFluentExtensions.cs
+++ b/src/MongoDB.Driver/IAggregateFluentExtensions.cs
@@ -95,7 +95,7 @@ public static IAggregateFluent> BucketAuto
- /// Appends a $bucketAuto stage to the pipeline.
+ /// Appends a $bucketAuto stage to the pipeline (this overload can only be used with LINQ3).
///
/// The type of the result.
/// The type of the value.
@@ -110,13 +110,46 @@ public static IAggregateFluent BucketAuto aggregate,
Expression> groupBy,
int buckets,
- Expression, TNewResult>> output,
+ Expression, TResult>, TNewResult>> output,
AggregateBucketAutoOptions options = null)
{
Ensure.IsNotNull(aggregate, nameof(aggregate));
+ if (aggregate.Database.Client.Settings.LinqProvider != LinqProvider.V3)
+ {
+ throw new InvalidOperationException("This overload of BucketAuto can only be used with LINQ3.");
+ }
+
return aggregate.AppendStage(PipelineStageDefinitionBuilder.BucketAuto(groupBy, buckets, output, options));
}
+ ///
+ /// Appends a $bucketAuto stage to the pipeline (this method can only be used with LINQ2).
+ ///
+ /// The type of the result.
+ /// The type of the value.
+ /// The type of the new result.
+ /// The aggregate.
+ /// The expression providing the value to group by.
+ /// The number of buckets.
+ /// The output projection.
+ /// The options (optional).
+ /// The fluent aggregate interface.
+ public static IAggregateFluent BucketAutoForLinq2(
+ this IAggregateFluent aggregate,
+ Expression> groupBy,
+ int buckets,
+ Expression, TNewResult>> output, // the IGrouping for BucketAuto has been wrong all along, only fixing it for LINQ3
+ AggregateBucketAutoOptions options = null)
+ {
+ Ensure.IsNotNull(aggregate, nameof(aggregate));
+ if (aggregate.Database.Client.Settings.LinqProvider != LinqProvider.V2)
+ {
+ throw new InvalidOperationException("The BucketAutoForLinq2 method can only be used with LINQ2.");
+ }
+
+ return aggregate.AppendStage(PipelineStageDefinitionBuilder.BucketAutoForLinq2(groupBy, buckets, output, options));
+ }
+
///
/// Appends a $densify stage to the pipeline.
///
@@ -396,15 +429,7 @@ public static IAggregateFluent Group(this IAggregateFluen
public static IAggregateFluent Group(this IAggregateFluent aggregate, Expression> id, Expression, TNewResult>> group)
{
Ensure.IsNotNull(aggregate, nameof(aggregate));
- if (aggregate.Database.Client.Settings.LinqProvider == LinqProvider.V2)
- {
- return aggregate.AppendStage(PipelineStageDefinitionBuilder.Group(id, group));
- }
- else
- {
- var (groupStage, projectStage) = PipelineStageDefinitionBuilder.GroupForLinq3(id, group);
- return aggregate.AppendStage(groupStage).AppendStage(projectStage);
- }
+ return aggregate.AppendStage(PipelineStageDefinitionBuilder.Group(id, group));
}
///
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstGroupPipelineOptimizer.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstGroupingPipelineOptimizer.cs
similarity index 83%
rename from src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstGroupPipelineOptimizer.cs
rename to src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstGroupingPipelineOptimizer.cs
index 53f2f991b44..400ebd8522a 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstGroupPipelineOptimizer.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstGroupingPipelineOptimizer.cs
@@ -24,33 +24,42 @@
namespace MongoDB.Driver.Linq.Linq3Implementation.Ast.Optimizers
{
- internal class AstGroupPipelineOptimizer
+ internal class AstGroupingPipelineOptimizer
{
#region static
public static AstPipeline Optimize(AstPipeline pipeline)
{
- var optimizer = new AstGroupPipelineOptimizer();
+ var optimizer = new AstGroupingPipelineOptimizer();
for (var i = 0; i < pipeline.Stages.Count; i++)
{
var stage = pipeline.Stages[i];
- if (stage is AstGroupStage groupStage)
+ if (IsGroupingStage(stage))
{
- pipeline = optimizer.OptimizeGroupStage(pipeline, i, groupStage);
+ pipeline = optimizer.OptimizeGroupingStage(pipeline, i, stage);
}
}
return pipeline;
+
+ static bool IsGroupingStage(AstStage stage)
+ {
+ return stage.NodeType switch
+ {
+ AstNodeType.GroupStage or AstNodeType.BucketStage or AstNodeType.BucketAutoStage => true,
+ _ => false
+ };
+ }
}
#endregion
private readonly AccumulatorSet _accumulators = new AccumulatorSet();
private AstExpression _element; // normally either "$$ROOT" or "$_v"
- private AstPipeline OptimizeGroupStage(AstPipeline pipeline, int i, AstGroupStage groupStage)
+ private AstPipeline OptimizeGroupingStage(AstPipeline pipeline, int i, AstStage groupingStage)
{
try
{
- if (IsOptimizableGroupStage(groupStage, out _element))
+ if (IsOptimizableGroupingStage(groupingStage, out _element))
{
var followingStages = GetFollowingStagesToOptimize(pipeline, i + 1);
if (followingStages == null)
@@ -58,7 +67,7 @@ private AstPipeline OptimizeGroupStage(AstPipeline pipeline, int i, AstGroupStag
return pipeline;
}
- var mappings = OptimizeGroupAndFollowingStages(groupStage, followingStages);
+ var mappings = OptimizeGroupingAndFollowingStages(groupingStage, followingStages);
if (mappings.Length > 0)
{
return (AstPipeline)AstNodeReplacer.Replace(pipeline, mappings);
@@ -72,23 +81,57 @@ private AstPipeline OptimizeGroupStage(AstPipeline pipeline, int i, AstGroupStag
return pipeline;
- static bool IsOptimizableGroupStage(AstGroupStage groupStage, out AstExpression element)
+ static bool IsOptimizableGroupingStage(AstStage groupingStage, out AstExpression element)
{
- // { $group : { _id : ?, _elements : { $push : element } } }
- if (groupStage.Fields.Count == 1)
+ if (groupingStage is AstGroupStage groupStage)
+ {
+ // { $group : { _id : ?, _elements : { $push : element } } }
+ if (groupStage.Fields.Count == 1)
+ {
+ var field = groupStage.Fields[0];
+ return IsElementsPush(field, out element);
+ }
+ }
+
+ if (groupingStage is AstBucketStage bucketStage)
+ {
+ // { $bucket : { groupBy : ?, boundaries : ?, default : ?, output : { _elements : { $push : element } } } }
+ if (bucketStage.Output.Count == 1)
+ {
+ var output = bucketStage.Output[0];
+ return IsElementsPush(output, out element);
+ }
+ }
+
+ if (groupingStage is AstBucketAutoStage bucketAutoStage)
{
- var field = groupStage.Fields[0];
- if (field.Path == "_elements" &&
+ // { $bucketAuto : { groupBy : ?, buckets : ?, granularity : ?, output : { _elements : { $push : element } } } }
+ if (bucketAutoStage.Output.Count == 1)
+ {
+ var output = bucketAutoStage.Output[0];
+ return IsElementsPush(output, out element);
+ }
+ }
+
+ element = null;
+ return false;
+
+ static bool IsElementsPush(AstAccumulatorField field, out AstExpression element)
+ {
+ if (
+ field.Path == "_elements" &&
field.Value is AstUnaryAccumulatorExpression unaryAccumulatorExpression &&
unaryAccumulatorExpression.Operator == AstUnaryAccumulatorOperator.Push)
{
element = unaryAccumulatorExpression.Arg;
return true;
}
+ else
+ {
+ element = null;
+ return false;
+ }
}
-
- element = null;
- return false;
}
static List GetFollowingStagesToOptimize(AstPipeline pipeline, int from)
@@ -135,7 +178,7 @@ static bool IsLastStageThatCanBeOptimized(AstStage stage)
}
}
- private (AstNode, AstNode)[] OptimizeGroupAndFollowingStages(AstGroupStage groupStage, List followingStages)
+ private (AstNode, AstNode)[] OptimizeGroupingAndFollowingStages(AstStage groupingStage, List followingStages)
{
var mappings = new List<(AstNode, AstNode)>();
@@ -148,10 +191,21 @@ static bool IsLastStageThatCanBeOptimized(AstStage stage)
}
}
- var newGroupStage = AstStage.Group(groupStage.Id, _accumulators);
- mappings.Add((groupStage, newGroupStage));
+ var newGroupingStage = CreateNewGroupingStage(groupingStage, _accumulators);
+ mappings.Add((groupingStage, newGroupingStage));
return mappings.ToArray();
+
+ static AstStage CreateNewGroupingStage(AstStage groupingStage, AccumulatorSet accumulators)
+ {
+ return groupingStage switch
+ {
+ AstGroupStage groupStage => AstStage.Group(groupStage.Id, accumulators),
+ AstBucketStage bucketStage => AstStage.Bucket(bucketStage.GroupBy, bucketStage.Boundaries, bucketStage.Default, accumulators),
+ AstBucketAutoStage bucketAutoStage => AstStage.BucketAuto(bucketAutoStage.GroupBy, bucketAutoStage.Buckets, bucketAutoStage.Granularity, accumulators),
+ _ => throw new Exception($"Unexpected {nameof(groupingStage)} node type: {groupingStage.NodeType}.")
+ };
+ }
}
private AstStage OptimizeFollowingStage(AstStage stage)
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstPipelineOptimizer.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstPipelineOptimizer.cs
index ccf430221e1..9cc5f8baf67 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstPipelineOptimizer.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstPipelineOptimizer.cs
@@ -19,7 +19,7 @@ internal static class AstPipelineOptimizer
{
public static AstPipeline Optimize(AstPipeline pipeline)
{
- pipeline = AstGroupPipelineOptimizer.Optimize(pipeline);
+ pipeline = AstGroupingPipelineOptimizer.Optimize(pipeline);
pipeline = AstSimplifier.SimplifyAndConvert(pipeline);
return pipeline;
}
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstBucketAutoStage.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstBucketAutoStage.cs
index 03bfab8f103..5aba7bf4019 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstBucketAutoStage.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstBucketAutoStage.cs
@@ -57,7 +57,7 @@ public override BsonValue Render()
{
return new BsonDocument
{
- { "$group", new BsonDocument
+ { "$bucketAuto", new BsonDocument
{
{ "groupBy", _groupBy.Render() },
{ "buckets", _buckets },
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstBucketStage.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstBucketStage.cs
index 30dc82d9b53..6bc4ef5135d 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstBucketStage.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstBucketStage.cs
@@ -57,7 +57,7 @@ public override BsonValue Render()
{
return new BsonDocument
{
- { "$group", new BsonDocument
+ { "$bucket", new BsonDocument
{
{ "groupBy", _groupBy.Render() },
{ "boundaries", new BsonArray(_boundaries) },
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/GroupExpressionStageDefinitions.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/GroupExpressionStageDefinitions.cs
deleted file mode 100644
index 39b30e36762..00000000000
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/GroupExpressionStageDefinitions.cs
+++ /dev/null
@@ -1,189 +0,0 @@
-/* Copyright 2010-present MongoDB Inc.
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Threading;
-using System.Threading.Tasks;
-using MongoDB.Bson.Serialization;
-using MongoDB.Driver.Core.Misc;
-using MongoDB.Driver.Linq.Linq3Implementation.Ast.Optimizers;
-using MongoDB.Driver.Linq.Linq3Implementation.Misc;
-using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
-using MongoDB.Driver.Linq.Linq3Implementation.Translators;
-using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToPipelineTranslators;
-
-namespace MongoDB.Driver.Linq.Linq3Implementation
-{
- internal sealed class GroupExpressionStageDefinitions
- {
- private readonly Expression> _idExpression;
- private readonly Expression, TOutput>> _groupExpression;
- private readonly GroupStageDefinition _groupStage;
- private readonly ProjectStageDefinition _projectStage;
-
- public GroupExpressionStageDefinitions(
- Expression> idExpression,
- Expression, TOutput>> groupExpression)
- {
- _idExpression = Ensure.IsNotNull(idExpression, nameof(idExpression));
- _groupExpression = Ensure.IsNotNull(groupExpression, nameof(groupExpression));
-
- _groupStage = new GroupStageDefinition(idExpression, groupExpression);
- _projectStage = new ProjectStageDefinition(_groupStage);
- }
-
- public Expression> IdExpression => _idExpression;
- public Expression, TOutput>> GroupExpression => _groupExpression;
- public PipelineStageDefinition> GroupStage => _groupStage;
- public PipelineStageDefinition, TOutput> ProjectStage => _projectStage;
-
- private class GroupStageDefinition : PipelineStageDefinition>
- {
- private readonly Expression> _idExpression;
- private readonly Expression, TOutput>> _groupExpression;
- private RenderedPipelineStageDefinition _renderedProjectStage = null;
-
- public GroupStageDefinition(
- Expression> idExpression,
- Expression, TOutput>> groupExpression)
- {
- _idExpression = idExpression;
- _groupExpression = groupExpression;
- }
-
- public override string OperatorName => "$group";
- public RenderedPipelineStageDefinition RenderedProjectStage => _renderedProjectStage;
-
- public override RenderedPipelineStageDefinition> Render(
- IBsonSerializer inputSerializer,
- IBsonSerializerRegistry serializerRegistry,
- LinqProvider linqProvider)
- {
- if (linqProvider != LinqProvider.V3)
- {
- throw new InvalidOperationException("GroupExpressionStageDefinitions can only be used with LINQ3.");
- }
-
- var expression = CreateExpression(inputSerializer);
- expression = PartialEvaluator.EvaluatePartially(expression);
- var context = TranslationContext.Create(expression, inputSerializer);
- var unoptimizedPipeline = ExpressionToPipelineTranslator.Translate(context, expression);
- var pipeline = AstPipelineOptimizer.Optimize(unoptimizedPipeline);
-
- var groupStageDocument = pipeline.Stages[0].Render().AsBsonDocument;
- var renderedGroupStage = new RenderedPipelineStageDefinition>("$group", groupStageDocument, new DummyIGroupingSerializer());
-
- var projectStageDocument = pipeline.Stages[1].Render().AsBsonDocument;
- _renderedProjectStage = new RenderedPipelineStageDefinition("$project", projectStageDocument, (IBsonSerializer)pipeline.OutputSerializer);
-
- return renderedGroupStage;
- }
-
- private Expression CreateExpression(IBsonSerializer inputSerializer)
- {
- var provider = new PseudoQueryProvider(inputSerializer);
- var pseudoSource = new PseudoSource(provider);
-
- var groupByExpression = Expression.Call(
- QueryableMethod.GroupByWithKeySelector.MakeGenericMethod(typeof(TInput), typeof(TKey)),
- Expression.Constant(pseudoSource),
- _idExpression);
-
- var selectExpression = Expression.Call(
- QueryableMethod.Select.MakeGenericMethod(typeof(IGrouping), typeof(TOutput)),
- groupByExpression,
- _groupExpression);
-
- return selectExpression;
- }
-
- private class PseudoQueryProvider : IMongoQueryProvider
- {
- private readonly IBsonSerializer _inputSerializer;
-
- public PseudoQueryProvider(IBsonSerializer inputSerializer)
- {
- _inputSerializer = inputSerializer;
- }
-
- public CollectionNamespace CollectionNamespace => throw new NotImplementedException();
- public IBsonSerializer PipelineInputSerializer => _inputSerializer;
-
- public IQueryable CreateQuery(Expression expression) => throw new NotImplementedException();
- public IQueryable CreateQuery(Expression expression) => throw new NotImplementedException();
- public object Execute(Expression expression) => throw new NotImplementedException();
- public TResult Execute(Expression expression) => throw new NotImplementedException();
- public Task ExecuteAsync(Expression expression, CancellationToken cancellationToken = default) => throw new NotImplementedException();
- public QueryableExecutionModel GetExecutionModel(Expression expression) => throw new NotImplementedException();
- }
-
- private class PseudoSource : IQueryable
- {
- private readonly Expression _expression;
- private readonly IQueryProvider _provider;
-
- public PseudoSource(IQueryProvider provider)
- {
- _provider = provider;
- _expression = Expression.Constant(this);
- }
-
- public Type ElementType => typeof(TInput);
-
- public Expression Expression => _expression;
- public IQueryProvider Provider => _provider;
-
- public IEnumerator GetEnumerator() => throw new NotImplementedException();
- IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
- }
-
- private class DummyIGroupingSerializer : IBsonSerializer>
- {
- public Type ValueType => typeof(IGrouping);
-
- public IGrouping Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => throw new NotImplementedException();
- public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, IGrouping value) => throw new NotImplementedException();
- public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) => throw new NotImplementedException();
- object IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => throw new NotImplementedException();
- }
- }
-
- private class ProjectStageDefinition : PipelineStageDefinition, TOutput>
- {
- private readonly GroupStageDefinition _groupStageDefinition;
-
- public ProjectStageDefinition(GroupStageDefinition groupStageDefinition)
- {
- _groupStageDefinition = groupStageDefinition;
- }
-
- public override string OperatorName => "$project";
-
- public override RenderedPipelineStageDefinition Render(IBsonSerializer> inputSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
- {
- var renderedProjectStage = _groupStageDefinition.RenderedProjectStage;
- if (renderedProjectStage == null)
- {
- throw new InvalidOperationException("GroupStageDefinition.Render must be called before ProjectStageDefinition.Render.");
- }
- return renderedProjectStage;
- }
- }
- }
-}
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/GroupingWithOutputExpressionStageDefinitions.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/GroupingWithOutputExpressionStageDefinitions.cs
new file mode 100644
index 00000000000..daf979aa615
--- /dev/null
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/GroupingWithOutputExpressionStageDefinitions.cs
@@ -0,0 +1,219 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using MongoDB.Bson.Serialization;
+using MongoDB.Driver.Linq;
+using MongoDB.Driver.Linq.Linq3Implementation.Ast;
+using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
+using MongoDB.Driver.Linq.Linq3Implementation.Ast.Optimizers;
+using MongoDB.Driver.Linq.Linq3Implementation.Ast.Stages;
+using MongoDB.Driver.Linq.Linq3Implementation.Misc;
+using MongoDB.Driver.Linq.Linq3Implementation.Serializers;
+using MongoDB.Driver.Linq.Linq3Implementation.Translators;
+using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators;
+
+namespace MongoDB.Driver.Linq.Linq3Implementation
+{
+ internal abstract class GroupingWithOutputExpressionStageDefinition : PipelineStageDefinition
+ {
+ protected readonly Expression> _output;
+
+ public GroupingWithOutputExpressionStageDefinition(Expression> output)
+ {
+ _output = output;
+ }
+
+ public override RenderedPipelineStageDefinition Render(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
+ {
+ if (linqProvider != LinqProvider.V3)
+ {
+ throw new InvalidOperationException($"{GetType().Name} is only intended for use with LINQ3.");
+ }
+
+ var groupingStage = RenderGroupingStage(inputSerializer, serializerRegistry, out var groupingSerializer);
+ var projectStage = RenderProjectStage(groupingSerializer, serializerRegistry, out var outputSerializer);
+ var optimizedStages = OptimizeGroupingStages(groupingStage, projectStage, inputSerializer, outputSerializer);
+ var renderedStages = optimizedStages.Select(x => x.Render().AsBsonDocument);
+
+ return new RenderedPipelineStageDefinition(OperatorName, renderedStages, outputSerializer);
+ }
+
+ protected abstract AstStage RenderGroupingStage(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry, out IBsonSerializer groupingOutputSerializer);
+
+ private AstStage RenderProjectStage(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry, out IBsonSerializer outputSerializer)
+ {
+ var partiallyEvaluatedOutput = (Expression>)PartialEvaluator.EvaluatePartially(_output);
+ var context = TranslationContext.Create(partiallyEvaluatedOutput, inputSerializer);
+ var outputTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, partiallyEvaluatedOutput, inputSerializer, asRoot: true);
+ var (projectStage, projectSerializer) = ProjectionHelper.CreateProjectStage(outputTranslation);
+ outputSerializer = (IBsonSerializer)projectSerializer;
+ return projectStage;
+ }
+
+ private IReadOnlyList OptimizeGroupingStages(AstStage groupingStage, AstStage projectStage, IBsonSerializer inputSerializer, IBsonSerializer outputSerializer)
+ {
+ var pipeline = AstPipeline.Empty(inputSerializer).AddStages(outputSerializer, groupingStage, projectStage);
+ var optimizedPipeline = AstPipelineOptimizer.Optimize(pipeline);
+ return optimizedPipeline.Stages;
+ }
+ }
+
+ internal sealed class BucketWithOutputExpressionStageDefinition : GroupingWithOutputExpressionStageDefinition, TOutput>
+ {
+ private readonly IReadOnlyList _boundaries;
+ private readonly Expression> _groupBy;
+ private readonly AggregateBucketOptions _options;
+ private readonly ExpressionTranslationOptions _translationOptions;
+
+ public BucketWithOutputExpressionStageDefinition(
+ Expression> groupBy,
+ IEnumerable boundaries,
+ Expression, TOutput>> output,
+ AggregateBucketOptions options,
+ ExpressionTranslationOptions translationOptions)
+ : base(output)
+ {
+ _groupBy = groupBy;
+ _boundaries = boundaries.ToArray();
+ _options = options;
+ _translationOptions = translationOptions;
+ }
+
+ public override string OperatorName => "$bucket";
+
+ public override RenderedPipelineStageDefinition Render(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
+ {
+ if (linqProvider == LinqProvider.V2)
+ {
+ var linq2Stage = PipelineStageDefinitionBuilder.Bucket(
+ new ExpressionAggregateExpressionDefinition(_groupBy, _translationOptions),
+ _boundaries,
+ new ExpressionBucketOutputProjection(x => default(TValue), _output, _translationOptions),
+ _options);
+ return linq2Stage.Render(inputSerializer, serializerRegistry, linqProvider);
+ }
+ else
+ {
+ return base.Render(inputSerializer, serializerRegistry, linqProvider);
+ }
+ }
+
+ protected override AstStage RenderGroupingStage(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry, out IBsonSerializer> groupingOutputSerializer)
+ {
+ var partiallyEvaluatedGroupBy = (Expression>)PartialEvaluator.EvaluatePartially(_groupBy);
+ var context = TranslationContext.Create(partiallyEvaluatedGroupBy, inputSerializer);
+ var groupByTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, partiallyEvaluatedGroupBy, inputSerializer, asRoot: true);
+
+ var valueSerializer = (IBsonSerializer)groupByTranslation.Serializer;
+ var serializedBoundaries = SerializationHelper.SerializeValues(valueSerializer, _boundaries);
+ var serializedDefault = _options != null && _options.DefaultBucket.HasValue ? SerializationHelper.SerializeValue(valueSerializer, _options.DefaultBucket.Value) : null;
+ var pushElements = AstExpression.AccumulatorField("_elements", AstUnaryAccumulatorOperator.Push, AstExpression.Var("ROOT", isCurrent: true));
+ groupingOutputSerializer = IGroupingSerializer.Create(valueSerializer, inputSerializer);
+
+ return AstStage.Bucket(
+ groupByTranslation.Ast,
+ serializedBoundaries,
+ serializedDefault,
+ new[] { pushElements });
+ }
+ }
+
+ internal sealed class BucketAutoWithOutputExpressionStageDefinition : GroupingWithOutputExpressionStageDefinition, TInput>, TOutput>
+ {
+ private readonly int _buckets;
+ private readonly Expression> _groupBy;
+ private readonly AggregateBucketAutoOptions _options;
+
+ public BucketAutoWithOutputExpressionStageDefinition(
+ Expression> groupBy,
+ int buckets,
+ Expression, TInput>, TOutput>> output,
+ AggregateBucketAutoOptions options)
+ : base(output)
+ {
+ _groupBy = groupBy;
+ _buckets = buckets;
+ _options = options;
+ }
+
+ public override string OperatorName => "$bucketAuto";
+
+ protected override AstStage RenderGroupingStage(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry, out IBsonSerializer, TInput>> groupingOutputSerializer)
+ {
+ var partiallyEvaluatedGroupBy = (Expression>)PartialEvaluator.EvaluatePartially(_groupBy);
+ var context = TranslationContext.Create(partiallyEvaluatedGroupBy, inputSerializer);
+ var groupByTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, partiallyEvaluatedGroupBy, inputSerializer, asRoot: true);
+
+ var valueSerializer = (IBsonSerializer)groupByTranslation.Serializer;
+ var keySerializer = AggregateBucketAutoResultIdSerializer.Create(valueSerializer);
+ var serializedGranularity = _options != null && _options.Granularity.HasValue ? _options.Granularity.Value.Value : null;
+ var pushElements = AstExpression.AccumulatorField("_elements", AstUnaryAccumulatorOperator.Push, AstExpression.Var("ROOT", isCurrent: true));
+ groupingOutputSerializer = IGroupingSerializer.Create(keySerializer, inputSerializer);
+
+ return AstStage.BucketAuto(
+ groupByTranslation.Ast,
+ _buckets,
+ serializedGranularity,
+ new[] { pushElements });
+ }
+ }
+
+ internal sealed class GroupWithOutputExpressionStageDefinition : GroupingWithOutputExpressionStageDefinition, TOutput>
+ {
+ private readonly Expression> _groupBy;
+ private readonly ExpressionTranslationOptions _translationOptions;
+
+ public GroupWithOutputExpressionStageDefinition(
+ Expression> groupBy,
+ Expression, TOutput>> output,
+ ExpressionTranslationOptions translationOptions = null)
+ : base(output)
+ {
+ _groupBy = groupBy;
+ _translationOptions = translationOptions;
+ }
+
+ public override string OperatorName => "$group";
+
+ public override RenderedPipelineStageDefinition Render(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
+ {
+ if (linqProvider == LinqProvider.V2)
+ {
+ var linq2Stage = PipelineStageDefinitionBuilder.Group(new GroupExpressionProjection(_groupBy, _output, _translationOptions));
+ return linq2Stage.Render(inputSerializer, serializerRegistry, linqProvider);
+ }
+ else
+ {
+ return base.Render(inputSerializer, serializerRegistry, linqProvider);
+ }
+ }
+
+ protected override AstStage RenderGroupingStage(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry, out IBsonSerializer> groupingOutputSerializer)
+ {
+ var partiallyEvaluatedGroupBy = (Expression>)PartialEvaluator.EvaluatePartially(_groupBy);
+ var context = TranslationContext.Create(partiallyEvaluatedGroupBy, inputSerializer);
+ var groupByTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, partiallyEvaluatedGroupBy, inputSerializer, asRoot: true);
+ var pushElements = AstExpression.AccumulatorField("_elements", AstUnaryAccumulatorOperator.Push, AstExpression.Var("ROOT", isCurrent: true));
+ var groupBySerializer = (IBsonSerializer)groupByTranslation.Serializer;
+ groupingOutputSerializer = IGroupingSerializer.Create(groupBySerializer, inputSerializer);
+
+ return AstStage.Group(groupByTranslation.Ast, pushElements);
+ }
+ }
+}
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/LinqProviderAdapterV3.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/LinqProviderAdapterV3.cs
index e8257ccfed2..1c98fbf091a 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/LinqProviderAdapterV3.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/LinqProviderAdapterV3.cs
@@ -71,8 +71,7 @@ internal override RenderedProjectionDefinition TranslateExpressionToBuc
IBsonSerializerRegistry serializerRegistry,
ExpressionTranslationOptions translationOptions)
{
- // TODO: implement using LINQ3 instead of falling back to LINQ2
- return LinqProviderAdapter.V2.TranslateExpressionToBucketOutputProjection(valueExpression, outputExpression, documentSerializer, serializerRegistry, translationOptions);
+ throw new InvalidOperationException("TranslateExpressionToBucketOutputProjection can only be used with LINQ2.");
}
internal override RenderedFieldDefinition TranslateExpressionToField(
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/IGroupingSerializer.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/IGroupingSerializer.cs
index d8f41451044..fe13ed0a33b 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/IGroupingSerializer.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/IGroupingSerializer.cs
@@ -107,5 +107,10 @@ public static IBsonSerializer Create(IBsonSerializer keySerializer, IBsonSeriali
var serializerType = typeof(IGroupingSerializer<,>).MakeGenericType(keyType, elementType);
return (IBsonSerializer)Activator.CreateInstance(serializerType, keySerializer, elementSerializer);
}
+
+ public static IBsonSerializer> Create(IBsonSerializer keySerializer, IBsonSerializer elementSerializer)
+ {
+ return new IGroupingSerializer(keySerializer, elementSerializer);
+ }
}
}
diff --git a/src/MongoDB.Driver/PipelineDefinition.cs b/src/MongoDB.Driver/PipelineDefinition.cs
index bac983d9cb6..2d26c40fb22 100644
--- a/src/MongoDB.Driver/PipelineDefinition.cs
+++ b/src/MongoDB.Driver/PipelineDefinition.cs
@@ -358,9 +358,12 @@ public override RenderedPipelineDefinition Render(IBsonSerializer 0)
+ foreach (var document in renderedStage.Documents)
{
- pipeline.Add(renderedStage.Document);
+ if (document.ElementCount > 0)
+ {
+ pipeline.Add(document);
+ }
}
}
diff --git a/src/MongoDB.Driver/PipelineDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineDefinitionBuilder.cs
index cd31f0014d2..a78b3f98af1 100644
--- a/src/MongoDB.Driver/PipelineDefinitionBuilder.cs
+++ b/src/MongoDB.Driver/PipelineDefinitionBuilder.cs
@@ -246,7 +246,7 @@ public static PipelineDefinition> Buck
}
///
- /// Appends a $bucketAuto stage to the pipeline.
+ /// Appends a $bucketAuto stage to the pipeline (this overload can only be used with LINQ3).
///
/// The type of the input documents.
/// The type of the intermediate documents.
@@ -265,7 +265,7 @@ public static PipelineDefinition BucketAuto pipeline,
Expression> groupBy,
int buckets,
- Expression, TOutput>> output,
+ Expression, TIntermediate>, TOutput>> output,
AggregateBucketAutoOptions options = null,
ExpressionTranslationOptions translationOptions = null)
{
@@ -273,6 +273,34 @@ public static PipelineDefinition BucketAuto
+ /// Appends a $bucketAuto stage to the pipeline (this method can only be used with LINQ2).
+ ///
+ /// The type of the input documents.
+ /// The type of the intermediate documents.
+ /// The type of the value.
+ /// The type of the output documents.
+ /// The pipeline.
+ /// The group by expression.
+ /// The number of buckets.
+ /// The output projection.
+ /// The options (optional).
+ /// The translation options.
+ ///
+ /// The fluent aggregate interface.
+ ///
+ public static PipelineDefinition BucketAutoForLinq2(
+ this PipelineDefinition pipeline,
+ Expression> groupBy,
+ int buckets,
+ Expression, TOutput>> output, // the IGrouping for BucketAuto has been wrong all along, only fixing it for LINQ3
+ AggregateBucketAutoOptions options = null,
+ ExpressionTranslationOptions translationOptions = null)
+ {
+ Ensure.IsNotNull(pipeline, nameof(pipeline));
+ return pipeline.AppendStage(PipelineStageDefinitionBuilder.BucketAutoForLinq2(groupBy, buckets, output, options, translationOptions));
+ }
+
///
/// Appends a $changeStream stage to the pipeline.
/// Normally you would prefer to use the Watch method of .
@@ -756,32 +784,6 @@ public static PipelineDefinition Group
- /// Appends a group stage to the pipeline (this method can only be used with LINQ3).
- ///
- /// The type of the input documents.
- /// The type of the intermediate documents.
- /// The type of the key.
- /// The type of the output documents.
- /// The pipeline.
- /// The id.
- /// The group projection.
- /// The translation options.
- ///
- /// The fluent aggregate interface.
- ///
- /// This method can only be used with LINQ3 but that can't be verified until Render is called.
- public static PipelineDefinition GroupForLinq3(
- this PipelineDefinition pipeline,
- Expression> id,
- Expression, TOutput>> group,
- ExpressionTranslationOptions translationOptions = null)
- {
- Ensure.IsNotNull(pipeline, nameof(pipeline));
- var (groupStage, projectStage) = PipelineStageDefinitionBuilder.GroupForLinq3(id, group, translationOptions);
- return pipeline.AppendStage(groupStage).AppendStage(projectStage);
- }
-
///
/// Appends a $limit stage to the pipeline.
///
@@ -1558,7 +1560,7 @@ public override RenderedPipelineDefinition Render(IBsonSerializer(documents, outputSerializer);
}
@@ -1634,7 +1636,7 @@ public override RenderedPipelineDefinition Render(IBsonSerializer(documents, outputSerializer);
}
diff --git a/src/MongoDB.Driver/PipelineStageDefinition.cs b/src/MongoDB.Driver/PipelineStageDefinition.cs
index 42f4430d522..f1f7388a1e2 100644
--- a/src/MongoDB.Driver/PipelineStageDefinition.cs
+++ b/src/MongoDB.Driver/PipelineStageDefinition.cs
@@ -14,6 +14,8 @@
*/
using System;
+using System.Collections.Generic;
+using System.Linq;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver.Core.Misc;
@@ -39,6 +41,11 @@ public interface IRenderedPipelineStageDefinition
///
BsonDocument Document { get; }
+ ///
+ /// Gets the documents (usually one but could be more).
+ ///
+ IReadOnlyList Documents { get; }
+
///
/// Gets the output serializer.
///
@@ -52,7 +59,7 @@ public interface IRenderedPipelineStageDefinition
public class RenderedPipelineStageDefinition : IRenderedPipelineStageDefinition
{
private string _operatorName;
- private BsonDocument _document;
+ private IReadOnlyList _documents;
private IBsonSerializer _outputSerializer;
///
@@ -62,16 +69,33 @@ public class RenderedPipelineStageDefinition : IRenderedPipelineStageDe
/// The document.
/// The output serializer.
public RenderedPipelineStageDefinition(string operatorName, BsonDocument document, IBsonSerializer outputSerializer)
+ : this(operatorName, new List { document }, outputSerializer)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Name of the pipeline operator.
+ /// The documents.
+ /// The output serializer.
+ public RenderedPipelineStageDefinition(string operatorName, IEnumerable documents, IBsonSerializer outputSerializer)
{
_operatorName = Ensure.IsNotNull(operatorName, nameof(operatorName));
- _document = Ensure.IsNotNull(document, nameof(document));
+ _documents = Ensure.IsNotNull(documents, nameof(documents)).ToArray();
_outputSerializer = Ensure.IsNotNull(outputSerializer, nameof(outputSerializer));
}
///
public BsonDocument Document
{
- get { return _document; }
+ get { return _documents.Single(); }
+ }
+
+ ///
+ public IReadOnlyList Documents
+ {
+ get { return _documents; }
}
///
@@ -233,7 +257,15 @@ public string ToString(IBsonSerializer inputSerializer, IBsonSerializerR
public string ToString(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
{
var renderedStage = Render(inputSerializer, serializerRegistry, linqProvider);
- return renderedStage.Document.ToJson();
+ var documents = renderedStage.Documents;
+ if (documents.Count == 1)
+ {
+ return documents[0].ToJson();
+ }
+ else
+ {
+ return new BsonArray(documents).ToJson();
+ }
}
string IPipelineStageDefinition.ToString(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry)
diff --git a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs
index 70d4c24bd13..49d1b126939 100644
--- a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs
+++ b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs
@@ -22,6 +22,7 @@
using MongoDB.Bson.Serialization;
using MongoDB.Driver.Core.Misc;
using MongoDB.Driver.Linq;
+using MongoDB.Driver.Linq.Linq3Implementation;
using MongoDB.Driver.Linq.Linq3Implementation.Serializers;
using MongoDB.Driver.Linq.Linq3Implementation.Translators;
using MongoDB.Driver.Search;
@@ -174,11 +175,7 @@ public static PipelineStageDefinition Bucket(groupBy, translationOptions),
- boundaries,
- new ExpressionBucketOutputProjection(x => default(TValue), output, translationOptions),
- options);
+ return new BucketWithOutputExpressionStageDefinition(groupBy, boundaries, output, options, translationOptions);
}
///
@@ -296,7 +293,7 @@ public static PipelineStageDefinition>
}
///
- /// Creates a $bucketAuto stage.
+ /// Creates a $bucketAuto stage (this overload can only be used with LINQ3).
///
/// The type of the input documents.
/// The type of the output documents.
@@ -310,7 +307,31 @@ public static PipelineStageDefinition>
public static PipelineStageDefinition BucketAuto(
Expression> groupBy,
int buckets,
- Expression, TOutput>> output,
+ Expression, TInput>, TOutput>> output,
+ AggregateBucketAutoOptions options = null,
+ ExpressionTranslationOptions translationOptions = null)
+ {
+ Ensure.IsNotNull(groupBy, nameof(groupBy));
+ Ensure.IsNotNull(output, nameof(output));
+ return new BucketAutoWithOutputExpressionStageDefinition(groupBy, buckets, output, options);
+ }
+
+ ///
+ /// Creates a $bucketAuto stage (this method can only be used with LINQ2).
+ ///
+ /// The type of the input documents.
+ /// The type of the output documents.
+ /// The type of the output documents.
+ /// The group by expression.
+ /// The number of buckets.
+ /// The output projection.
+ /// The options (optional).
+ /// The translation options.
+ /// The stage.
+ public static PipelineStageDefinition BucketAutoForLinq2(
+ Expression> groupBy,
+ int buckets,
+ Expression, TOutput>> output, // the IGrouping for BucketAuto has been wrong all along, only fixing it for LINQ3
AggregateBucketAutoOptions options = null,
ExpressionTranslationOptions translationOptions = null)
{
@@ -866,29 +887,7 @@ public static PipelineStageDefinition Group(value, group, translationOptions));
- }
-
- ///
- /// Creates a $group stage (this method can only be used with LINQ3).
- ///
- /// The type of the input documents.
- /// The type of the values.
- /// The type of the output documents.
- /// The value field.
- /// The group projection.
- /// The translation options.
- /// The stage.
- /// This method can only be used with LINQ3 but that can't be verified until Render is called.
- public static GroupForLinq3Result GroupForLinq3(
- Expression> value,
- Expression, TOutput>> group,
- ExpressionTranslationOptions translationOptions = null)
- {
- Ensure.IsNotNull(value, nameof(value));
- Ensure.IsNotNull(group, nameof(group));
- var stages = new Linq.Linq3Implementation.GroupExpressionStageDefinitions(value, group);
- return new GroupForLinq3Result(stages.GroupStage, stages.ProjectStage);
+ return new GroupWithOutputExpressionStageDefinition(value, group);
}
///
@@ -1899,6 +1898,11 @@ public Expression, TOutput>> OutputExpression
public override RenderedProjectionDefinition Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
{
+ if (linqProvider != LinqProvider.V2)
+ {
+ throw new InvalidOperationException("ExpressionBucketOutputProjection can only be used with LINQ2.");
+ }
+
return linqProvider.GetAdapter().TranslateExpressionToBucketOutputProjection(_valueExpression, _outputExpression, documentSerializer, serializerRegistry, _translationOptions);
}
}
diff --git a/tests/MongoDB.Driver.Tests/AggregateFluentBucketAutoTests.cs b/tests/MongoDB.Driver.Tests/AggregateFluentBucketAutoTests.cs
index dbf467c6c42..b6936b3d959 100644
--- a/tests/MongoDB.Driver.Tests/AggregateFluentBucketAutoTests.cs
+++ b/tests/MongoDB.Driver.Tests/AggregateFluentBucketAutoTests.cs
@@ -14,6 +14,7 @@
*/
using System;
+using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using MongoDB.Bson;
@@ -21,13 +22,15 @@
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Bson.TestHelpers;
-using MongoDB.Driver.Core.Misc;
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
+using MongoDB.Driver.Linq;
+using MongoDB.Driver.Tests.Linq.Linq3ImplementationTests;
+using MongoDB.TestHelpers.XunitExtensions;
using Xunit;
namespace MongoDB.Driver.Tests
{
- public class AggregateFluentBucketAutoTests
+ public class AggregateFluentBucketAutoTests : Linq3IntegrationTest
{
#region static
// private static fields
@@ -211,48 +214,90 @@ public void BucketAuto_typed_should_return_expected_result()
new AggregateBucketAutoResult(1926, 1926, 1));
}
- [Fact]
- public void BucketAuto_typed_with_output_should_add_expected_stage()
+ [Theory]
+ [ParameterAttributeData]
+ public void BucketAuto_typed_with_output_should_add_expected_stage(
+ [Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
{
- var collection = __database.GetCollection(__collectionNamespace.CollectionName);
+ var collection = GetCollection(linqProvider: linqProvider);
var subject = collection.Aggregate();
var buckets = 4;
- var result = subject.BucketAuto(
- e => e.Year,
- buckets,
- g => new { _id = default(AggregateBucketAutoResultId), Years = g.Select(e => e.Year), Count = g.Count() });
-
- var stage = result.Stages.Single();
- var serializerRegistry = BsonSerializer.SerializerRegistry;
- var exhibitSerializer = serializerRegistry.GetSerializer();
- var renderedStage = stage.Render(exhibitSerializer, serializerRegistry);
- renderedStage.Document.Should().Be("{ $bucketAuto : { groupBy : \"$year\", buckets : 4, output : { Years : { $push : \"$year\" }, Count : { $sum : 1 } } } }");
+ if (linqProvider == LinqProvider.V2)
+ {
+ var result = subject.BucketAutoForLinq2(
+ e => e.Year,
+ buckets,
+ g => new { _id = default(AggregateBucketAutoResultId), Years = g.Select(e => e.Year), Count = g.Count() });
+
+ var stage = result.Stages.Single();
+ var serializerRegistry = BsonSerializer.SerializerRegistry;
+ var exhibitSerializer = serializerRegistry.GetSerializer();
+ var renderedStage = stage.Render(exhibitSerializer, serializerRegistry, linqProvider);
+ renderedStage.Document.Should().Be("{ $bucketAuto : { groupBy : \"$year\", buckets : 4, output : { Years : { $push : \"$year\" }, Count : { $sum : 1 } } } }");
+ }
+ else
+ {
+ var result = subject.BucketAuto(
+ e => (int?)e.Year,
+ buckets,
+ g => new { Key = g.Key, Years = g.Select(e => e.Year), Count = g.Count() });
+
+ var stage = result.Stages.Single();
+ var serializerRegistry = BsonSerializer.SerializerRegistry;
+ var exhibitSerializer = serializerRegistry.GetSerializer();
+ var renderedStage = stage.Render(exhibitSerializer, serializerRegistry, linqProvider);
+ renderedStage.Documents.Should().HaveCount(2);
+ renderedStage.Documents[0].Should().Be("{ $bucketAuto : { groupBy : '$year', buckets : 4, output : { __agg0 : { $push : '$year' }, __agg1 : { $sum : 1 } } } }");
+ renderedStage.Documents[1].Should().Be("{ $project : { Key : '$_id', Years : '$__agg0', Count : '$__agg1', _id : 0 } }");
+ }
}
- [Fact]
- public void BucketAuto_typed_with_output_should_return_expected_result()
+ [Theory]
+ [ParameterAttributeData]
+ public void BucketAuto_typed_with_output_should_return_expected_result(
+ [Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
{
RequireServer.Check();
EnsureTestData();
- var collection = __database.GetCollection(__collectionNamespace.CollectionName);
+ var collection = GetCollection(linqProvider: linqProvider);
var subject = collection.Aggregate();
var buckets = 4;
- var result = subject
- .BucketAuto(
- e => e.Year,
- buckets,
- g => new { _id = default(AggregateBucketAutoResultId), Years = g.Select(e => e.Year), Count = g.Count() })
- .ToList();
-
- result.Select(r => r._id.Min).Should().Equal(null, 1902, 1925, 1926);
- result.Select(r => r._id.Max).Should().Equal(1902, 1925, 1926, 1926);
- result[0].Years.Should().Equal(new int[0]);
- result[1].Years.Should().Equal(new int[] { 1902 });
- result[2].Years.Should().Equal(new int[] { 1925 });
- result[3].Years.Should().Equal(new int[] { 1926 });
- result.Select(r => r.Count).Should().Equal(1, 1, 1, 1);
+ if (linqProvider == LinqProvider.V2)
+ {
+ var result = subject
+ .BucketAutoForLinq2(
+ e => e.Year,
+ buckets,
+ g => new { _id = default(AggregateBucketAutoResultId), Years = g.Select(e => e.Year), Count = g.Count() })
+ .ToList();
+
+ result.Select(r => r._id.Min).Should().Equal(null, 1902, 1925, 1926);
+ result.Select(r => r._id.Max).Should().Equal(1902, 1925, 1926, 1926);
+ result[0].Years.Should().Equal(new int[0]);
+ result[1].Years.Should().Equal(new int[] { 1902 });
+ result[2].Years.Should().Equal(new int[] { 1925 });
+ result[3].Years.Should().Equal(new int[] { 1926 });
+ result.Select(r => r.Count).Should().Equal(1, 1, 1, 1);
+ }
+ else
+ {
+ var result = subject
+ .BucketAuto(
+ e => (int?)e.Year,
+ buckets,
+ g => new { Key = g.Key, Years = g.Select(e => e.Year), Count = g.Count() })
+ .ToList();
+
+ result.Select(r => r.Key.Min).Should().Equal(null, 1902, 1925, 1926);
+ result.Select(r => r.Key.Max).Should().Equal(1902, 1925, 1926, 1926);
+ result[0].Years.Should().Equal(new int[0]);
+ result[1].Years.Should().Equal(new int[] { 1902 });
+ result[2].Years.Should().Equal(new int[] { 1925 });
+ result[3].Years.Should().Equal(new int[] { 1926 });
+ result.Select(r => r.Count).Should().Equal(1, 1, 1, 1);
+ }
}
// nested types
diff --git a/tests/MongoDB.Driver.Tests/AggregateFluentBucketTests.cs b/tests/MongoDB.Driver.Tests/AggregateFluentBucketTests.cs
index 25b1ac8fa7e..006f493ac8d 100644
--- a/tests/MongoDB.Driver.Tests/AggregateFluentBucketTests.cs
+++ b/tests/MongoDB.Driver.Tests/AggregateFluentBucketTests.cs
@@ -21,14 +21,15 @@
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Bson.TestHelpers;
-using MongoDB.Driver.Core.Misc;
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
using MongoDB.Driver.Linq;
+using MongoDB.Driver.Tests.Linq.Linq3ImplementationTests;
+using MongoDB.TestHelpers.XunitExtensions;
using Xunit;
namespace MongoDB.Driver.Tests
{
- public class AggregateFluentBucketTests
+ public class AggregateFluentBucketTests : Linq3IntegrationTest
{
#region static
// private static fields
@@ -179,50 +180,92 @@ public void Bucket_typed_should_return_expected_result()
new AggregateBucketResult("Unknown", 1));
}
- [Fact]
- public void Bucket_typed_with_output_should_add_expected_stage()
+ [Theory]
+ [ParameterAttributeData]
+ public void Bucket_typed_with_output_should_add_expected_stage(
+ [Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
{
- var collection = __database.GetCollection(__collectionNamespace.CollectionName);
+ var collection = GetCollection(linqProvider: linqProvider);
var subject = collection.Aggregate();
var boundaries = new BsonValue[] { 1900, 1920, 1950 };
var options = new AggregateBucketOptions { DefaultBucket = (BsonValue)"Unknown" };
- var result = subject.Bucket(
- e => e.Year,
- boundaries,
- g => new { _id = default(BsonValue), Years = g.Select(e => e.Year), Count = g.Count() },
- options);
-
- var stage = result.Stages.Single();
- var serializerRegistry = BsonSerializer.SerializerRegistry;
- var exhibitSerializer = serializerRegistry.GetSerializer();
- var renderedStage = stage.Render(exhibitSerializer, serializerRegistry);
- renderedStage.Document.Should().Be("{ $bucket : { groupBy : \"$year\", boundaries : [ 1900, 1920, 1950 ], default : \"Unknown\", output : { Years : { $push : \"$year\" }, Count : { $sum : 1 } } } }");
+ if (linqProvider == LinqProvider.V2)
+ {
+ var result = subject.Bucket(
+ e => e.Year,
+ boundaries,
+ g => new { _id = default(BsonValue), Years = g.Select(e => e.Year), Count = g.Count() },
+ options);
+
+ var stage = result.Stages.Single();
+ var serializerRegistry = BsonSerializer.SerializerRegistry;
+ var exhibitSerializer = serializerRegistry.GetSerializer();
+ var renderedStage = stage.Render(exhibitSerializer, serializerRegistry, linqProvider);
+ renderedStage.Document.Should().Be("{ $bucket : { groupBy : \"$year\", boundaries : [ 1900, 1920, 1950 ], default : \"Unknown\", output : { Years : { $push : \"$year\" }, Count : { $sum : 1 } } } }");
+ }
+ else
+ {
+ var result = subject.Bucket(
+ e => e.Year,
+ boundaries,
+ g => new { Key = g.Key, Years = g.Select(e => e.Year), Count = g.Count() },
+ options);
+
+ var stage = result.Stages.Single();
+ var serializerRegistry = BsonSerializer.SerializerRegistry;
+ var exhibitSerializer = serializerRegistry.GetSerializer();
+ var renderedStage = stage.Render(exhibitSerializer, serializerRegistry, linqProvider);
+ renderedStage.Documents.Should().HaveCount(2);
+ renderedStage.Documents[0].Should().Be("{ $bucket : { groupBy : '$year', boundaries : [ 1900, 1920, 1950 ], default : 'Unknown', output : { __agg0 : {$push : '$year' }, __agg1 : { $sum : 1 } } } }");
+ renderedStage.Documents[1].Should().Be("{ $project : { Key : '$_id', Years : '$__agg0', Count : '$__agg1', _id : 0 } }");
+ }
}
- [Fact]
- public void Bucket_typed_with_output_should_return_expected_result()
+ [Theory]
+ [ParameterAttributeData]
+ public void Bucket_typed_with_output_should_return_expected_result(
+ [Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
{
RequireServer.Check();
EnsureTestData();
- var collection = __database.GetCollection(__collectionNamespace.CollectionName);
+ var collection = GetCollection(linqProvider: linqProvider);
var subject = collection.Aggregate();
var boundaries = new BsonValue[] { 1900, 1920, 1950 };
var options = new AggregateBucketOptions { DefaultBucket = (BsonValue)"Unknown" };
- var result = subject
- .Bucket(
- e => e.Year,
- boundaries,
- g => new { _id = default(BsonValue), Years = g.Select(e => e.Year), Count = g.Count() },
- options)
- .ToList();
-
- result.Select(b => b._id).Should().Equal(1900, 1920, "Unknown");
- result[0].Years.Should().Equal(new[] { 1902 });
- result[1].Years.Should().Equal(new[] { 1926, 1925 });
- result[2].Years.Should().Equal(new int[0]);
- result.Select(b => b.Count).Should().Equal(1, 2, 1);
+ if (linqProvider== LinqProvider.V2)
+ {
+ var result = subject
+ .Bucket(
+ e => e.Year,
+ boundaries,
+ g => new { _id = default(BsonValue), Years = g.Select(e => e.Year), Count = g.Count() },
+ options)
+ .ToList();
+
+ result.Select(b => b._id).Should().Equal(1900, 1920, "Unknown");
+ result[0].Years.Should().Equal(new[] { 1902 });
+ result[1].Years.Should().Equal(new[] { 1926, 1925 });
+ result[2].Years.Should().Equal(new int[0]);
+ result.Select(b => b.Count).Should().Equal(1, 2, 1);
+ }
+ else
+ {
+ var result = subject
+ .Bucket(
+ e => e.Year,
+ boundaries,
+ g => new { Key = g.Key, Years = g.Select(e => e.Year), Count = g.Count() },
+ options)
+ .ToList();
+
+ result.Select(b => b.Key).Should().Equal(1900, 1920, "Unknown");
+ result[0].Years.Should().Equal(new[] { 1902 });
+ result[1].Years.Should().Equal(new[] { 1926, 1925 });
+ result[2].Years.Should().Equal(new int[0]);
+ result.Select(b => b.Count).Should().Equal(1, 2, 1);
+ }
}
// nested types
diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp3933Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp3933Tests.cs
index 81813cc458b..f2c9e42a784 100644
--- a/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp3933Tests.cs
+++ b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp3933Tests.cs
@@ -15,12 +15,10 @@
using System;
using System.Linq;
-using FluentAssertions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Serializers;
-using MongoDB.TestHelpers.XunitExtensions;
using MongoDB.Driver.Linq;
-using MongoDB.Driver.Linq.Linq3Implementation.Serializers;
+using MongoDB.TestHelpers.XunitExtensions;
using Xunit;
namespace MongoDB.Driver.Tests.Linq.Linq3ImplementationTests.Jira
@@ -126,56 +124,27 @@ public void PipelineDefinitionBuilder_Group_with_projection_to_TOutput_should_wo
Linq3TestHelpers.AssertStages(stages, expectedStages);
}
- [Fact]
- public void PipelineDefinitionBuilder_Group_with_expressions_should_work_with_LINQ2()
- {
- var emptyPipeline = (PipelineDefinition)new EmptyPipelineDefinition();
-
- var pipeline = emptyPipeline.Group(x => 1, x => new { Count = x.Count() });
-
- var stages = Linq3TestHelpers.Render(pipeline, BsonDocumentSerializer.Instance, LinqProvider.V2);
- var expectedStages = new[]
- {
- "{ $group : { _id : 1, Count : { $sum : 1 } } }"
- };
- Linq3TestHelpers.AssertStages(stages, expectedStages);
- }
-
- [Fact]
- public void PipelineDefinitionBuilder_Group_with_expressions_should_throw_with_LINQ3()
+ [Theory]
+ [ParameterAttributeData]
+ public void PipelineDefinitionBuilder_Group_with_expressions_should_work(
+ [Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
{
var emptyPipeline = (PipelineDefinition)new EmptyPipelineDefinition();
var pipeline = emptyPipeline.Group(x => 1, x => new { Count = x.Count() });
- var exception = Record.Exception(() => Linq3TestHelpers.Render(pipeline, BsonDocumentSerializer.Instance, LinqProvider.V3));
- exception.Should().BeOfType();
- }
-
- [Fact]
- public void PipelineDefinitionBuilder_GroupForLinq3_with_expressions_should_throw_with_LINQ2()
- {
- var emptyPipeline = (PipelineDefinition)new EmptyPipelineDefinition();
-
- var pipeline = emptyPipeline.GroupForLinq3(x => 1, x => new { Count = x.Count() });
-
- var exception = Record.Exception(() => Linq3TestHelpers.Render(pipeline, BsonDocumentSerializer.Instance, LinqProvider.V2));
- exception.Should().BeOfType();
- }
-
- [Fact]
- public void PipelineDefinitionBuilder_GroupForLinq3_with_expressions_should_work_with_LINQ3()
- {
- var emptyPipeline = (PipelineDefinition)new EmptyPipelineDefinition();
-
- var pipeline = emptyPipeline.GroupForLinq3(x => 1, x => new { Count = x.Count() });
-
- var stages = Linq3TestHelpers.Render(pipeline, BsonDocumentSerializer.Instance, LinqProvider.V3);
- var expectedStages = new[]
- {
- "{ $group : { _id : 1, __agg0 : { $sum : 1 } } }",
- "{ $project : { Count : '$__agg0', _id : 0 } }"
- };
+ var stages = Linq3TestHelpers.Render(pipeline, BsonDocumentSerializer.Instance, linqProvider);
+ var expectedStages = linqProvider == LinqProvider.V2 ?
+ new[]
+ {
+ "{ $group : { _id : 1, Count : { $sum : 1 } } }"
+ }
+ :
+ new[]
+ {
+ "{ $group : { _id : 1, __agg0 : { $sum : 1 } } }",
+ "{ $project : { Count : '$__agg0', _id : 0 } }"
+ };
Linq3TestHelpers.AssertStages(stages, expectedStages);
}
@@ -186,12 +155,12 @@ public void PipelineStageDefinitionBuilder_Group_with_projection_to_implied_Bson
{
var stageDefinition = PipelineStageDefinitionBuilder.Group("{ _id : 1, Count : { $sum : 1 } }");
- var stage = Linq3TestHelpers.Render(stageDefinition, BsonDocumentSerializer.Instance, linqProvider);
+ var stages = Linq3TestHelpers.Render(stageDefinition, BsonDocumentSerializer.Instance, linqProvider);
var expectedStages = new[]
{
"{ $group : { _id : 1, Count : { $sum : 1 } } }"
};
- Linq3TestHelpers.AssertStages(new[] { stage }, expectedStages);
+ Linq3TestHelpers.AssertStages(stages, expectedStages);
}
[Theory]
@@ -201,59 +170,34 @@ public void PipelineStageDefinitionBuilder_Group_with_projection_to_TOutput_shou
{
var stageDefinition = PipelineStageDefinitionBuilder.Group("{ _id : 1, Count : { $sum : 1 } }");
- var stage = Linq3TestHelpers.Render(stageDefinition, BsonDocumentSerializer.Instance, linqProvider);
+ var stages = Linq3TestHelpers.Render(stageDefinition, BsonDocumentSerializer.Instance, linqProvider);
var expectedStages = new[]
{
"{ $group : { _id : 1, Count : { $sum : 1 } } }"
};
- Linq3TestHelpers.AssertStages(new[] { stage }, expectedStages);
- }
-
- [Fact]
- public void PipelineStageDefinitionBuilder_Group_with_expressions_should_work_with_LINQ2()
- {
- var stageDefinition = PipelineStageDefinitionBuilder.Group((BsonDocument x) => 1, x => new { Count = x.Count() });
-
- var stage = Linq3TestHelpers.Render(stageDefinition, BsonDocumentSerializer.Instance, LinqProvider.V2);
- var expectedStages = new[]
- {
- "{ $group : { _id : 1, Count : { $sum : 1 } } }"
- };
- Linq3TestHelpers.AssertStages(new[] { stage }, expectedStages);
+ Linq3TestHelpers.AssertStages(stages, expectedStages);
}
- [Fact]
- public void PipelineStageDefinitionBuilder_Group_with_expressions_should_throw_with_LINQ3()
+ [Theory]
+ [ParameterAttributeData]
+ public void PipelineStageDefinitionBuilder_Group_with_expressions_should_work(
+ [Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
{
var stageDefinition = PipelineStageDefinitionBuilder.Group((BsonDocument x) => 1, x => new { Count = x.Count() });
- var exception = Record.Exception(() => Linq3TestHelpers.Render(stageDefinition, BsonDocumentSerializer.Instance, LinqProvider.V3));
- exception.Should().BeOfType();
- }
-
- [Fact]
- public void PipelineStageDefinitionBuilder_GroupForLinq3_with_expressions_should_throw_with_LINQ2()
- {
- var (groupStageDefinition, projectStageDefinition) = PipelineStageDefinitionBuilder.GroupForLinq3((BsonDocument x) => 1, x => new { Count = x.Count() });
-
- var exception = Record.Exception(() => Linq3TestHelpers.Render(groupStageDefinition, BsonDocumentSerializer.Instance, LinqProvider.V2));
- exception.Should().BeOfType();
- }
-
- [Fact]
- public void PipelineStageDefinitionBuilderGroupForLinq3_with_expressions_should_work_with_LINQ3()
- {
- var (groupStageDefinition, projectStageDefinition) = PipelineStageDefinitionBuilder.GroupForLinq3((BsonDocument x) => 1, x => new { Count = x.Count() });
-
- var groupStage = Linq3TestHelpers.Render(groupStageDefinition, BsonDocumentSerializer.Instance, LinqProvider.V3);
- var groupingSerializer = new IGroupingSerializer(new Int32Serializer(), BsonDocumentSerializer.Instance);
- var projectStage = Linq3TestHelpers.Render(projectStageDefinition, groupingSerializer, LinqProvider.V3);
- var expectedStages = new[]
- {
- "{ $group : { _id : 1, __agg0 : { $sum : 1 } } }",
- "{ $project : { Count : '$__agg0', _id : 0 } }"
- };
- Linq3TestHelpers.AssertStages(new[] { groupStage, projectStage }, expectedStages);
+ var stages = Linq3TestHelpers.Render(stageDefinition, BsonDocumentSerializer.Instance, linqProvider);
+ var expectedStages = linqProvider == LinqProvider.V2 ?
+ new[]
+ {
+ "{ $group : { _id : 1, Count : { $sum : 1 } } }"
+ }
+ :
+ new[]
+ {
+ "{ $group : { _id : 1, __agg0 : { $sum : 1 } } }",
+ "{ $project : { Count : '$__agg0', _id : 0 } }"
+ };
+ Linq3TestHelpers.AssertStages(stages, expectedStages);
}
private IMongoCollection GetCollection(LinqProvider linqProvider)
diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Linq3TestHelpers.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Linq3TestHelpers.cs
index f97b79aaa54..1369d6656de 100644
--- a/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Linq3TestHelpers.cs
+++ b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Linq3TestHelpers.cs
@@ -37,10 +37,10 @@ public static IList Render(PipelineDefinition(PipelineStageDefinition stage, IBsonSerializer inputSerializer, LinqProvider linqProvider)
+ public static IReadOnlyList Render(PipelineStageDefinition stage, IBsonSerializer inputSerializer, LinqProvider linqProvider)
{
var rendered = stage.Render(inputSerializer, BsonSerializer.SerializerRegistry, linqProvider);
- return rendered.Document;
+ return rendered.Documents;
}
public static List Translate(IMongoCollection collection, IAggregateFluent aggregate)
diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/LinqProviderV3Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/LinqProviderV3Tests.cs
index ec37f9f9eb5..b37de455038 100644
--- a/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/LinqProviderV3Tests.cs
+++ b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/LinqProviderV3Tests.cs
@@ -16,7 +16,6 @@
using System;
using System.Linq;
using System.Linq.Expressions;
-using System.Threading;
using FluentAssertions;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
@@ -62,7 +61,7 @@ public void TranslateExpressionToAggregateExpression_should_return_expected_resu
}
[Fact]
- public void TranslateExpressionToBucketOutputProjection_should_return_expected_result()
+ public void TranslateExpressionToBucketOutputProjection_should_throw()
{
var subject = LinqProviderAdapter.V3;
Expression> valueExpression = c => c.X;
@@ -71,13 +70,9 @@ public void TranslateExpressionToBucketOutputProjection_should_return_expected_r
var documentSerializer = serializerRegistry.GetSerializer();
var translationOptions = new ExpressionTranslationOptions();
- var result = subject.TranslateExpressionToBucketOutputProjection(valueExpression, outputExpression, documentSerializer, serializerRegistry, translationOptions);
+ var exception = Record.Exception(() => subject.TranslateExpressionToBucketOutputProjection(valueExpression, outputExpression, documentSerializer, serializerRegistry, translationOptions));
- var expectedResult = LinqProviderAdapter.V2.TranslateExpressionToBucketOutputProjection(valueExpression, outputExpression, documentSerializer, serializerRegistry, translationOptions);
- expectedResult.Document.Should().Be("{ $sum : 1 }");
- expectedResult.ProjectionSerializer.Should().BeOfType();
- result.Document.Should().Be(expectedResult.Document);
- result.ProjectionSerializer.Should().BeOfType(expectedResult.ProjectionSerializer.GetType());
+ exception.Should().BeOfType();
}
[Fact]