diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/FirstOrLastMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/FirstOrLastMethodToAggregationExpressionTranslator.cs index 932d4c6856b..87b0a10c706 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/FirstOrLastMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/FirstOrLastMethodToAggregationExpressionTranslator.cs @@ -16,6 +16,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions; using MongoDB.Driver.Linq.Linq3Implementation.Misc; using MongoDB.Driver.Linq.Linq3Implementation.Reflection; @@ -95,16 +96,30 @@ public static TranslatedExpression Translate(TranslationContext context, MethodC var sourceAst = sourceTranslation.Ast; var itemSerializer = ArraySerializerHelper.GetItemSerializer(sourceTranslation.Serializer); + var isFirstMethod = method.IsOneOf(__firstMethods); + if (method.IsOneOf(__withPredicateMethods)) { var predicateLambda = ExpressionHelper.UnquoteLambdaIfQueryableMethod(method, arguments[1]); var parameterExpression = predicateLambda.Parameters.Single(); var parameterSymbol = context.CreateSymbol(parameterExpression, itemSerializer, isCurrent: false); var predicateTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, predicateLambda, parameterSymbol); + + AstExpression limit = null; + if (isFirstMethod) + { + var compatibilityLevel = context.TranslationOptions.CompatibilityLevel; + if (Feature.FilterLimit.IsSupported(compatibilityLevel.ToWireVersion())) + { + limit = AstExpression.Constant(1); + } + } + sourceAst = AstExpression.Filter( input: sourceAst, cond: predicateTranslation.Ast, - @as: parameterSymbol.Var.Name); + @as: parameterSymbol.Var.Name, + limit: limit); } AstExpression ast; @@ -119,11 +134,11 @@ public static TranslatedExpression Translate(TranslationContext context, MethodC @in: AstExpression.Cond( @if: AstExpression.Eq(AstExpression.Size(valuesAst), 0), then: serializedDefaultValue, - @else: method.IsOneOf(__firstMethods) ? AstExpression.First(valuesAst) : AstExpression.Last(valuesAst))); + @else: isFirstMethod ? AstExpression.First(valuesAst) : AstExpression.Last(valuesAst))); } else { - ast = method.Name == "First" ? AstExpression.First(sourceAst) : AstExpression.Last(sourceAst); + ast = isFirstMethod ? AstExpression.First(sourceAst) : AstExpression.Last(sourceAst); } return new TranslatedExpression(expression, ast, itemSerializer); diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4048Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4048Tests.cs index a6bb95b69b1..de09357af80 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4048Tests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4048Tests.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Linq; using FluentAssertions; +using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Linq; using MongoDB.Driver.TestHelpers; using Xunit; @@ -24,6 +25,8 @@ namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira { public class CSharp4048Tests : LinqIntegrationTest { + private static readonly bool FilterLimitIsSupported = Feature.FilterLimit.IsSupported(CoreTestConfiguration.MaxWireVersion); + public CSharp4048Tests(ClassFixture fixture) : base(fixture) { @@ -118,7 +121,7 @@ public void List_get_Item_of_scalar_should_work() var expectedStages = new[] { "{ $group : { _id : '$_id', _elements : { $push: '$X' } } }", - "{ $project : { _id : '$_id' Result : { $arrayElemAt : ['$_elements', 0] } } }", + "{ $project : { _id : '$_id', Result : { $arrayElemAt : ['$_elements', 0] } } }", "{ $sort : { _id : 1 } }" }; AssertStages(stages, expectedStages); @@ -1040,12 +1043,21 @@ public void IGrouping_First_with_predicate_of_root_should_work() .OrderBy(x => x.Id); var stages = Translate(collection, queryable); - var expectedStages = new[] - { - "{ $group : { _id : '$_id', _elements : { $push : '$$ROOT' } } }", - "{ $project : { _id : '$_id', Result : { $arrayElemAt : [{ $filter : { input : '$_elements', as : 'e', cond : { $ne : ['$$e.X', 1] } } }, 0] } } }", - "{ $sort : { _id : 1 } }" - }; + + var expectedStages = FilterLimitIsSupported + ? new[] + { + "{ $group : { _id : '$_id', _elements : { $push : '$$ROOT' } } }", + "{ $project : { _id : '$_id', Result : { $arrayElemAt : [{ $filter : { input : '$_elements', as : 'e', cond : { $ne : ['$$e.X', 1] }, limit : 1 } }, 0] } } }", + "{ $sort : { _id : 1 } }" + } + : new[] + { + "{ $group : { _id : '$_id', _elements : { $push : '$$ROOT' } } }", + "{ $project : { _id : '$_id', Result : { $arrayElemAt : [{ $filter : { input : '$_elements', as : 'e', cond : { $ne : ['$$e.X', 1] } } }, 0] } } }", + "{ $sort : { _id : 1 } }" + }; + AssertStages(stages, expectedStages); var results = queryable.ToList(); @@ -1065,12 +1077,21 @@ public void IGrouping_First_with_predicate_of_scalar_should_work() .OrderBy(x => x.Id); var stages = Translate(collection, queryable); - var expectedStages = new[] - { - "{ $group : { _id : '$_id', _elements : { $push : '$X' } } }", - "{ $project : { _id : '$_id', Result : { $arrayElemAt : [{ $filter : { input : '$_elements', as : 'e', cond : { $ne : ['$$e', 1] } } }, 0] } } }", - "{ $sort : { _id : 1 } }" - }; + + var expectedStages = FilterLimitIsSupported + ? new[] + { + "{ $group : { _id : '$_id', _elements : { $push : '$X' } } }", + "{ $project : { _id : '$_id', Result : { $arrayElemAt : [{ $filter : { input : '$_elements', as : 'e', cond : { $ne : ['$$e', 1] }, limit : 1 } }, 0] } } }", + "{ $sort : { _id : 1 } }" + } + : new[] + { + "{ $group : { _id : '$_id', _elements : { $push : '$X' } } }", + "{ $project : { _id : '$_id', Result : { $arrayElemAt : [{ $filter : { input : '$_elements', as : 'e', cond : { $ne : ['$$e', 1] } } }, 0] } } }", + "{ $sort : { _id : 1 } }" + }; + AssertStages(stages, expectedStages); var results = queryable.ToList(); @@ -1140,12 +1161,21 @@ public void IGrouping_FirstOrDefault_with_predicate_of_root_should_work() .OrderBy(x => x.Id); var stages = Translate(collection, queryable); - var expectedStages = new[] - { - "{ $group : { _id : '$_id', _elements : { $push : '$$ROOT' } } }", - "{ $project : { _id : '$_id', Result : { $let : { vars : { values : { $filter : { input : '$_elements', as : 'e', cond : { $ne : ['$$e.X', 1] } } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : null, else : { $arrayElemAt : ['$$values', 0] } } } } } } }", - "{ $sort : { _id : 1 } }" - }; + + var expectedStages = FilterLimitIsSupported + ? new[] + { + "{ $group : { _id : '$_id', _elements : { $push : '$$ROOT' } } }", + "{ $project : { _id : '$_id', Result : { $let : { vars : { values : { $filter : { input : '$_elements', as : 'e', cond : { $ne : ['$$e.X', 1] }, limit : 1 } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : null, else : { $arrayElemAt : ['$$values', 0] } } } } } } }", + "{ $sort : { _id : 1 } }" + } + : new[] + { + "{ $group : { _id : '$_id', _elements : { $push : '$$ROOT' } } }", + "{ $project : { _id : '$_id', Result : { $let : { vars : { values : { $filter : { input : '$_elements', as : 'e', cond : { $ne : ['$$e.X', 1] } } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : null, else : { $arrayElemAt : ['$$values', 0] } } } } } } }", + "{ $sort : { _id : 1 } }" + }; + AssertStages(stages, expectedStages); var results = queryable.ToList(); @@ -1166,12 +1196,21 @@ public void IGrouping_FirstOrDefault_with_predicate_of_scalar_should_work() .OrderBy(x => x.Id); var stages = Translate(collection, queryable); - var expectedStages = new[] - { - "{ $group : { _id : '$_id', _elements : { $push : '$X' } } }", - "{ $project : { _id : '$_id', Result : { $let : { vars : { values : { $filter : { input : '$_elements', as : 'e', cond : { $ne : ['$$e', 1] } } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : 0, else : { $arrayElemAt : ['$$values', 0] } } } } } } }", - "{ $sort : { _id : 1 } }" - }; + + var expectedStages = FilterLimitIsSupported + ? new[] + { + "{ $group : { _id : '$_id', _elements : { $push : '$X' } } }", + "{ $project : { _id : '$_id', Result : { $let : { vars : { values : { $filter : { input : '$_elements', as : 'e', cond : { $ne : ['$$e', 1] }, limit : 1 } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : 0, else : { $arrayElemAt : ['$$values', 0] } } } } } } }", + "{ $sort : { _id : 1 } }" + } + : new[] + { + "{ $group : { _id : '$_id', _elements : { $push : '$X' } } }", + "{ $project : { _id : '$_id', Result : { $let : { vars : { values : { $filter : { input : '$_elements', as : 'e', cond : { $ne : ['$$e', 1] } } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : 0, else : { $arrayElemAt : ['$$values', 0] } } } } } } }", + "{ $sort : { _id : 1 } }" + }; + AssertStages(stages, expectedStages); var results = queryable.ToList(); diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5258Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5258Tests.cs index d0a65b23bce..0a0c7f50692 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5258Tests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5258Tests.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Linq; using FluentAssertions; +using MongoDB.Driver.Core.Misc; using MongoDB.Driver.TestHelpers; using Xunit; @@ -37,7 +38,15 @@ public void Select_First_with_predicate_should_work() .Select(_x => _x.List.First(_y => _y > 2)); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$List', as : 'v__0', cond : { $gt : ['$$v__0', 2] } } }, 0] }, _id : 0 } }"); + + if (Feature.FilterLimit.IsSupported(CoreTestConfiguration.MaxWireVersion)) + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$List', as : 'v__0', cond : { $gt : ['$$v__0', 2] }, limit : 1 } }, 0] }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$List', as : 'v__0', cond : { $gt : ['$$v__0', 2] } } }, 0] }, _id : 0 } }"); + } var results = queryable.ToList(); results.Should().Equal(3, 4); diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5779Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5779Tests.cs index cc7a78eb37c..7d8b8d54f4a 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5779Tests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5779Tests.cs @@ -21,6 +21,7 @@ using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Options; using MongoDB.Bson.Serialization.Serializers; +using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Linq.Linq3Implementation.Serializers; using Xunit; @@ -28,6 +29,8 @@ namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira; public class CSharp5779Tests : LinqIntegrationTest { + private static readonly bool FilterLimitIsSupported = Feature.FilterLimit.IsSupported(CoreTestConfiguration.MaxWireVersion); + static CSharp5779Tests() { BsonClassMap.RegisterClassMap(cm => @@ -73,7 +76,15 @@ public void DictionaryAsArrayOfArrays_Keys_First_with_predicate_should_work() .Select(x => x.DictionaryAsArrayOfArrays.Keys.First(k => k == "b")); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : '$DictionaryAsArrayOfArrays', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 0] } } }, as : 'k', cond : { $eq : ['$$k', 'b'] } } }, 0] }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : '$DictionaryAsArrayOfArrays', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 0] } } }, as : 'k', cond : { $eq : ['$$k', 'b'] }, limit : 1 } }, 0] }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : '$DictionaryAsArrayOfArrays', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 0] } } }, as : 'k', cond : { $eq : ['$$k', 'b'] } } }, 0] }, _id : 0 } }"); + } var results = queryable.ToList(); results.Count.Should().Be(4); @@ -92,7 +103,7 @@ public void DictionaryAsArrayOfArrays_Values_should_work() .Select(x => x.DictionaryAsArrayOfArrays.Values); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $map : { input : '$DictionaryAsArrayOfArrays', as : 'kvp' in : { k : { $arrayElemAt : ['$$kvp', 0] }, v : { $arrayElemAt : ['$$kvp', 1] } } } } , _id : 0 } }"); + AssertStages(stages, "{ $project : { _v : { $map : { input : '$DictionaryAsArrayOfArrays', as : 'kvp', in : { k : { $arrayElemAt : ['$$kvp', 0] }, v : { $arrayElemAt : ['$$kvp', 1] } } } }, _id : 0 } }"); var results = queryable.ToList(); results.Count.Should().Be(4); @@ -111,7 +122,15 @@ public void DictionaryAsArrayOfArrays_Values_First_with_predicate_should_work() .Select(x => x.DictionaryAsArrayOfArrays.Values.First(v => v == 2)); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : { $map : { input : '$DictionaryAsArrayOfArrays', as : 'kvp', in : { k : { $arrayElemAt : ['$$kvp', 0] }, v : { $arrayElemAt : ['$$kvp', 1] } } } }, as : 'v', cond : { $eq : ['$$v.v', 2] } } }, 0] } }, in : '$$this.v' } }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : { $map : { input : '$DictionaryAsArrayOfArrays', as : 'kvp', in : { k : { $arrayElemAt : ['$$kvp', 0] }, v : { $arrayElemAt : ['$$kvp', 1] } } } }, as : 'v', cond : { $eq : ['$$v.v', 2] }, limit : 1 } }, 0] } }, in : '$$this.v' } }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : { $map : { input : '$DictionaryAsArrayOfArrays', as : 'kvp', in : { k : { $arrayElemAt : ['$$kvp', 0] }, v : { $arrayElemAt : ['$$kvp', 1] } } } }, as : 'v', cond : { $eq : ['$$v.v', 2] } } }, 0] } }, in : '$$this.v' } }, _id : 0 } }"); + } var results = queryable.ToList(); results.Count.Should().Be(4); @@ -149,7 +168,15 @@ public void DictionaryAsArrayOfDocuments_Keys_First_with_predicate_should_work() .Select(x => x.DictionaryAsArrayOfDocuments.Keys.First(k => k == "b")); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$DictionaryAsArrayOfDocuments.k', as : 'k', cond : { $eq : ['$$k', 'b'] } } }, 0] }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$DictionaryAsArrayOfDocuments.k', as : 'k', cond : { $eq : ['$$k', 'b'] }, limit : 1 } }, 0] }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$DictionaryAsArrayOfDocuments.k', as : 'k', cond : { $eq : ['$$k', 'b'] } } }, 0] }, _id : 0 } }"); + } var results = queryable.ToList(); results.Count.Should().Be(4); @@ -187,7 +214,15 @@ public void DictionaryAsArrayOfDocuments_Values_First_with_predicate_should_work .Select(x => x.DictionaryAsArrayOfDocuments.Values.First(v => v == 2)); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$DictionaryAsArrayOfDocuments', as : 'v', cond : { $eq : ['$$v.v', 2] } } }, 0] } }, in : '$$this.v' } }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$DictionaryAsArrayOfDocuments', as : 'v', cond : { $eq : ['$$v.v', 2] }, limit : 1 } }, 0] } }, in : '$$this.v' } }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$DictionaryAsArrayOfDocuments', as : 'v', cond : { $eq : ['$$v.v', 2] } } }, 0] } }, in : '$$this.v' } }, _id : 0 } }"); + } var results = queryable.ToList(); results.Count.Should().Be(4); @@ -225,7 +260,15 @@ public void DictionaryAsDocument_Keys_First_with_predicate_should_work() .Select(x => x.DictionaryAsDocument.Keys.First(k => k == "b")); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : { $objectToArray : '$DictionaryAsDocument' }, as : 'kvp', in : '$$kvp.k' } }, as : 'k', cond : { $eq : ['$$k', 'b'] } } }, 0] }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : { $objectToArray : '$DictionaryAsDocument' }, as : 'kvp', in : '$$kvp.k' } }, as : 'k', cond : { $eq : ['$$k', 'b'] }, limit : 1 } }, 0] }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : { $objectToArray : '$DictionaryAsDocument' }, as : 'kvp', in : '$$kvp.k' } }, as : 'k', cond : { $eq : ['$$k', 'b'] } } }, 0] }, _id : 0 } }"); + } var results = queryable.ToList(); results.Count.Should().Be(4); @@ -263,7 +306,15 @@ public void DictionaryAsDocument_Values_First_with_predicate_should_work() .Select(x => x.DictionaryAsDocument.Values.First(v => v == 2)); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : { $objectToArray : '$DictionaryAsDocument' }, as : 'v', cond : { $eq : ['$$v.v', 2] } } }, 0] } }, in : '$$this.v' } }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : { $objectToArray : '$DictionaryAsDocument' }, as : 'v', cond : { $eq : ['$$v.v', 2] }, limit : 1 } }, 0] } }, in : '$$this.v' } }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : { $objectToArray : '$DictionaryAsDocument' }, as : 'v', cond : { $eq : ['$$v.v', 2] } } }, 0] } }, in : '$$this.v' } }, _id : 0 } }"); + } var results = queryable.ToList(); results.Count.Should().Be(4); @@ -301,7 +352,15 @@ public void IDictionaryAsArrayOfArrays_Keys_First_with_predicate_should_work() .Select(x => x.IDictionaryAsArrayOfArrays.Keys.First(k => k == "b")); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : '$IDictionaryAsArrayOfArrays', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 0] } } }, as : 'k', cond : { $eq : ['$$k', 'b'] } } }, 0] }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : '$IDictionaryAsArrayOfArrays', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 0] } } }, as : 'k', cond : { $eq : ['$$k', 'b'] }, limit : 1 } }, 0] }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : '$IDictionaryAsArrayOfArrays', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 0] } } }, as : 'k', cond : { $eq : ['$$k', 'b'] } } }, 0] }, _id : 0 } }"); + } var results = queryable.ToList(); results.Count.Should().Be(4); @@ -339,7 +398,15 @@ public void IDictionaryAsArrayOfArrays_Values_First_with_predicate_should_work() .Select(x => x.IDictionaryAsArrayOfArrays.Values.First(v => v == 2)); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : '$IDictionaryAsArrayOfArrays', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 1] } } }, as : 'v', cond : { $eq : ['$$v', 2] } } }, 0] }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : '$IDictionaryAsArrayOfArrays', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 1] } } }, as : 'v', cond : { $eq : ['$$v', 2] }, limit : 1 } }, 0] }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : '$IDictionaryAsArrayOfArrays', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 1] } } }, as : 'v', cond : { $eq : ['$$v', 2] } } }, 0] }, _id : 0 } }"); + } var results = queryable.ToList(); results.Count.Should().Be(4); @@ -377,7 +444,15 @@ public void IDictionaryAsArrayOfDocuments_Keys_First_with_predicate_should_work( .Select(x => x.IDictionaryAsArrayOfDocuments.Keys.First(k => k == "b")); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$IDictionaryAsArrayOfDocuments.k', as : 'k', cond : { $eq : ['$$k', 'b'] } } }, 0] }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$IDictionaryAsArrayOfDocuments.k', as : 'k', cond : { $eq : ['$$k', 'b'] }, limit : 1 } }, 0] }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$IDictionaryAsArrayOfDocuments.k', as : 'k', cond : { $eq : ['$$k', 'b'] } } }, 0] }, _id : 0 } }"); + } var results = queryable.ToList(); results.Count.Should().Be(4); @@ -415,7 +490,15 @@ public void IDictionaryAsArrayOfDocuments_Values_First_with_predicate_should_wor .Select(x => x.IDictionaryAsArrayOfDocuments.Values.First(v => v == 2)); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$IDictionaryAsArrayOfDocuments.v', as : 'v', cond : { $eq : ['$$v', 2] } } }, 0] }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$IDictionaryAsArrayOfDocuments.v', as : 'v', cond : { $eq : ['$$v', 2] }, limit : 1 } }, 0] }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$IDictionaryAsArrayOfDocuments.v', as : 'v', cond : { $eq : ['$$v', 2] } } }, 0] }, _id : 0 } }"); + } var results = queryable.ToList(); results.Count.Should().Be(4); @@ -453,7 +536,15 @@ public void IDictionaryAsDocument_Keys_First_with_predicate_should_work() .Select(x => x.IDictionaryAsDocument.Keys.First(k => k == "b")); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : { $objectToArray : '$IDictionaryAsDocument' }, as : 'kvp', in : '$$kvp.k' } }, as : 'k', cond : { $eq : ['$$k', 'b'] } } }, 0] }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : { $objectToArray : '$IDictionaryAsDocument' }, as : 'kvp', in : '$$kvp.k' } }, as : 'k', cond : { $eq : ['$$k', 'b'] }, limit : 1 } }, 0] }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : { $objectToArray : '$IDictionaryAsDocument' }, as : 'kvp', in : '$$kvp.k' } }, as : 'k', cond : { $eq : ['$$k', 'b'] } } }, 0] }, _id : 0 } }"); + } var results = queryable.ToList(); results.Count.Should().Be(4); @@ -491,7 +582,15 @@ public void IDictionaryAsDocument_Values_First_with_predicate_should_work() .Select(x => x.IDictionaryAsDocument.Values.First(v => v == 2)); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : { $objectToArray : '$IDictionaryAsDocument' }, as : 'kvp', in : '$$kvp.v' } }, as : 'v', cond : { $eq : ['$$v', 2] } } }, 0] }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : { $objectToArray : '$IDictionaryAsDocument' }, as : 'kvp', in : '$$kvp.v' } }, as : 'v', cond : { $eq : ['$$v', 2] }, limit : 1 } }, 0] }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : { $map : { input : { $objectToArray : '$IDictionaryAsDocument' }, as : 'kvp', in : '$$kvp.v' } }, as : 'v', cond : { $eq : ['$$v', 2] } } }, 0] }, _id : 0 } }"); + } var results = queryable.ToList(); results.Count.Should().Be(4); @@ -510,7 +609,7 @@ public void DictionaryAsDocumentOfNestedDictionaryAsArrayOfArrays_Values_SelectM .Select(x => x.DictionaryAsDocumentOfNestedDictionaryAsArrayOfArrays.Values.SelectMany(n => n.Keys)); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $reduce : { input : { $map : { input : { $objectToArray : '$DictionaryAsDocumentOfNestedDictionaryAsArrayOfArrays' }, as : 'n', in : { $map : { input : '$$n.v', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 0] } } } } }, initialValue : [], in : { $concatArrays : ['$$value', '$$this'] } } }, _id : 0 } }"); + AssertStages(stages, "{ $project : { _v : { $reduce : { input : { $map : { input : { $objectToArray : '$DictionaryAsDocumentOfNestedDictionaryAsArrayOfArrays' }, as : 'n', in : { $map : { input : '$$n.v', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 0] } } } } }, initialValue : [], in : { $concatArrays : ['$$value', '$$this'] } } }, _id : 0 } }"); var results = queryable.ToList(); results.Count.Should().Be(4); diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/FirstOrLastMethodToAggregationExpressionTranslatorTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/FirstOrLastMethodToAggregationExpressionTranslatorTests.cs index b131aeadb99..e4f120773af 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/FirstOrLastMethodToAggregationExpressionTranslatorTests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/FirstOrLastMethodToAggregationExpressionTranslatorTests.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Linq; using FluentAssertions; +using MongoDB.Driver.Core.Misc; using MongoDB.Driver.TestHelpers; using MongoDB.TestHelpers.XunitExtensions; using Xunit; @@ -24,6 +25,8 @@ namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Translators.ExpressionTo { public class FirstOrLastMethodToAggregationExpressionTranslatorTests : LinqIntegrationTest { + private static readonly bool FilterLimitIsSupported = Feature.FilterLimit.IsSupported(CoreTestConfiguration.MaxWireVersion); + public FirstOrLastMethodToAggregationExpressionTranslatorTests(ClassFixture fixture) : base(fixture) { @@ -59,7 +62,15 @@ public void First_with_predicate_should_work( collection.AsQueryable().Select(x => x.A.First(x => x > 1)); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$A', as : 'x', cond : { $gt : ['$$x', 1] } } }, 0] }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$A', as : 'x', cond : { $gt : ['$$x', 1] }, limit : 1 } }, 0] }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $filter : { input : '$A', as : 'x', cond : { $gt : ['$$x', 1] } } }, 0] }, _id : 0 } }"); + } var results = queryable.ToList(); results.Should().Equal(0, 0, 2, 2); @@ -95,7 +106,15 @@ public void FirstOrDefault_with_predicate_should_work( collection.AsQueryable().Select(x => x.A.FirstOrDefault(x => x > 1)); var stages = Translate(collection, queryable); - AssertStages(stages, "{ $project : { _v : { $let : { vars : { values : { $filter : { input : '$A', as : 'x', cond : { $gt : ['$$x', 1] } } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : 0, else : { $arrayElemAt : ['$$values', 0] } } } } }, _id : 0 } }"); + + if (FilterLimitIsSupported) + { + AssertStages(stages, "{ $project : { _v : { $let : { vars : { values : { $filter : { input : '$A', as : 'x', cond : { $gt : ['$$x', 1] }, limit : 1 } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : 0, else : { $arrayElemAt : ['$$values', 0] } } } } }, _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : { $let : { vars : { values : { $filter : { input : '$A', as : 'x', cond : { $gt : ['$$x', 1] } } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : 0, else : { $arrayElemAt : ['$$values', 0] } } } } }, _id : 0 } }"); + } var results = queryable.ToList(); results.Should().Equal(0, 0, 2, 2);