Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,6 +25,8 @@ namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
{
public class CSharp4048Tests : LinqIntegrationTest<CSharp4048Tests.ClassFixture>
{
private static readonly bool FilterLimitIsSupported = Feature.FilterLimit.IsSupported(CoreTestConfiguration.MaxWireVersion);

public CSharp4048Tests(ClassFixture fixture)
: base(fixture)
{
Expand Down Expand Up @@ -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] } } }",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How was this test not failing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea. I just happened to catch it.

"{ $project : { _id : '$_id', Result : { $arrayElemAt : ['$_elements', 0] } } }",
"{ $sort : { _id : 1 } }"
};
AssertStages(stages, expectedStages);
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I almost find the longer version more readable:

var expectedStages = Feature.FilterLimit.IsSupported(CoreTestConfiguration.MaxWireVersion)

because I don't have to chase down how FilterLimitIsSupported is computed.

In some other files you still use the long form.

Copy link
Contributor Author

@adelinowona adelinowona Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather keep the FilterLimitIsSupported. I didn't want to have to keep repeating the longer form everywhere especially when it is the same information that we can get once and just reuse everywhere.

In some other files. there is only one use case, which is why I just used the longer form directly.

? 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();
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using MongoDB.Driver.Core.Misc;
using MongoDB.Driver.TestHelpers;
using Xunit;

Expand All @@ -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);
Expand Down
Loading