From 31c43e0f88e2246feb3928144da9d46122ea26c3 Mon Sep 17 00:00:00 2001 From: Oleksandr Poliakov Date: Fri, 3 Jan 2025 16:32:45 -0800 Subject: [PATCH 1/3] CSHARP-5436: Optimize special case of Any with constant array and field equals parameter in predicate --- .../Ast/Optimizers/AstSimplifier.cs | 44 +++++++++++ .../Linq3Implementation/Jira/AnyEqualTests.cs | 79 +++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/AnyEqualTests.cs diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstSimplifier.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstSimplifier.cs index d9332aeb399..493e76383d1 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstSimplifier.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstSimplifier.cs @@ -103,6 +103,50 @@ static bool OperatorMapsNullToNull(AstUnaryOperator @operator) } } + public override AstNode VisitExprFilter(AstExprFilter node) + { + var optimizedNode = (AstExprFilter)base.VisitExprFilter(node); + + if (optimizedNode.Expression is AstUnaryExpression unaryExpression && + unaryExpression.Operator == AstUnaryOperator.AnyElementTrue && + unaryExpression.Arg is AstMapExpression mapExpression && + mapExpression.Input is AstConstantExpression inputConstant && + inputConstant.Value is BsonArray inputArrayValue && + mapExpression.In is AstBinaryExpression inBinaryExpression && + inBinaryExpression.Operator == AstBinaryOperator.Eq && + TryGetBinaryExpressionArguments(inBinaryExpression, out AstFieldPathExpression fieldPathExpression, out AstVarExpression varExpression) && + fieldPathExpression.Path.Length > 1 && fieldPathExpression.Path[0] == '$' && fieldPathExpression.Path[1] != '$' && + varExpression == mapExpression.As) + { + return AstFilter.In(AstFilter.Field(fieldPathExpression.Path.Substring(1)), inputArrayValue); + } + + return optimizedNode; + + static bool TryGetBinaryExpressionArguments(AstBinaryExpression binaryExpression, out T1 arg1, out T2 arg2) + where T1 : AstNode + where T2 : AstNode + { + if (binaryExpression.Arg1 is T1 arg1AsT1 && binaryExpression.Arg2 is T2 arg2AsT2) + { + arg1 = arg1AsT1; + arg2 = arg2AsT2; + return true; + } + + if (binaryExpression.Arg1 is T2 arg1AsT2 && binaryExpression.Arg1 is T1 arg2AsT1) + { + arg1 = arg2AsT1; + arg2 = arg1AsT2; + return true; + } + + arg1 = null; + arg2 = null; + return false; + } + } + public override AstNode VisitFieldOperationFilter(AstFieldOperationFilter node) { node = (AstFieldOperationFilter)base.VisitFieldOperationFilter(node); diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/AnyEqualTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/AnyEqualTests.cs new file mode 100644 index 00000000000..2fed4d5477d --- /dev/null +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/AnyEqualTests.cs @@ -0,0 +1,79 @@ +/* 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; +using FluentAssertions; +using Xunit; + +namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira +{ + public class AnyEqualTests : Linq3IntegrationTest + { + [Fact] + public void Any_equal_should_translate_to_in() + { + var collection = GetCollection(); + + var obj = new[] { 1, 2, 3 }; + var queryable = collection.AsQueryable() + .Where(x => obj.Any(y => x.X == y)); + + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match : { X : { $in : [1, 2, 3] } } }"); + + var results = queryable.ToList(); + results.Select(x => x.Id).Should().Equal(1); + } + + [Fact] + public void Any_equal_should_translate_to_in_nested_object() + { + var collection = GetCollection(); + + var obj = new[] { 1, 2, 3 }; + var queryable = collection.AsQueryable() + .Where(x => obj.Any(y => x.Doc.X == y)); + + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match : { 'Doc.X' : { $in : [1, 2, 3] } } }"); + + var results = queryable.ToList(); + results.Select(x => x.Id).Should().Equal(1); + } + + private IMongoCollection GetCollection() + { + var collection = GetCollection("test"); + CreateCollection( + collection, + new C { Id = 1, X = 1, Doc = new N { X = 1 }}, + new C { Id = 2, X = 4, Doc = new N { X = 4 } }); + return collection; + } + + private class C + { + public int Id { get; set; } + public int X { get; set; } + + public N Doc { get; set; } + } + + private class N + { + public int X { get; set; } + } + } +} From ec9d1f4992fa730259432e2b340a36e9e1981161 Mon Sep 17 00:00:00 2001 From: Oleksandr Poliakov Date: Mon, 6 Jan 2025 13:47:44 -0800 Subject: [PATCH 2/3] PR --- .../Linq3Implementation/Ast/Optimizers/AstSimplifier.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstSimplifier.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstSimplifier.cs index 493e76383d1..2fb234fbd9f 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstSimplifier.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstSimplifier.cs @@ -105,9 +105,10 @@ static bool OperatorMapsNullToNull(AstUnaryOperator @operator) public override AstNode VisitExprFilter(AstExprFilter node) { - var optimizedNode = (AstExprFilter)base.VisitExprFilter(node); + var optimizedNode = base.VisitExprFilter(node); - if (optimizedNode.Expression is AstUnaryExpression unaryExpression && + if (optimizedNode is AstExprFilter exprFilter && + exprFilter.Expression is AstUnaryExpression unaryExpression && unaryExpression.Operator == AstUnaryOperator.AnyElementTrue && unaryExpression.Arg is AstMapExpression mapExpression && mapExpression.Input is AstConstantExpression inputConstant && @@ -118,6 +119,8 @@ mapExpression.In is AstBinaryExpression inBinaryExpression && fieldPathExpression.Path.Length > 1 && fieldPathExpression.Path[0] == '$' && fieldPathExpression.Path[1] != '$' && varExpression == mapExpression.As) { + // { $expr : { $anyElementTrue : { $map : { input : , as : "", in : { $eq : ["$", "$$"] } } } } } + // => { "" : { $in : } } return AstFilter.In(AstFilter.Field(fieldPathExpression.Path.Substring(1)), inputArrayValue); } From 59b52590cc6f7644102a7f167c16ab6836df91a7 Mon Sep 17 00:00:00 2001 From: Oleksandr Poliakov Date: Mon, 6 Jan 2025 17:56:46 -0800 Subject: [PATCH 3/3] PR --- .../Linq3Implementation/Jira/AnyEqualTests.cs | 79 ------------------- ...dToAggregationExpressionTranslatorTests.cs | 16 ++++ 2 files changed, 16 insertions(+), 79 deletions(-) delete mode 100644 tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/AnyEqualTests.cs diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/AnyEqualTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/AnyEqualTests.cs deleted file mode 100644 index 2fed4d5477d..00000000000 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/AnyEqualTests.cs +++ /dev/null @@ -1,79 +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; -using FluentAssertions; -using Xunit; - -namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira -{ - public class AnyEqualTests : Linq3IntegrationTest - { - [Fact] - public void Any_equal_should_translate_to_in() - { - var collection = GetCollection(); - - var obj = new[] { 1, 2, 3 }; - var queryable = collection.AsQueryable() - .Where(x => obj.Any(y => x.X == y)); - - var stages = Translate(collection, queryable); - AssertStages(stages, "{ $match : { X : { $in : [1, 2, 3] } } }"); - - var results = queryable.ToList(); - results.Select(x => x.Id).Should().Equal(1); - } - - [Fact] - public void Any_equal_should_translate_to_in_nested_object() - { - var collection = GetCollection(); - - var obj = new[] { 1, 2, 3 }; - var queryable = collection.AsQueryable() - .Where(x => obj.Any(y => x.Doc.X == y)); - - var stages = Translate(collection, queryable); - AssertStages(stages, "{ $match : { 'Doc.X' : { $in : [1, 2, 3] } } }"); - - var results = queryable.ToList(); - results.Select(x => x.Id).Should().Equal(1); - } - - private IMongoCollection GetCollection() - { - var collection = GetCollection("test"); - CreateCollection( - collection, - new C { Id = 1, X = 1, Doc = new N { X = 1 }}, - new C { Id = 2, X = 4, Doc = new N { X = 4 } }); - return collection; - } - - private class C - { - public int Id { get; set; } - public int X { get; set; } - - public N Doc { get; set; } - } - - private class N - { - public int X { get; set; } - } - } -} diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/AnyMethodToAggregationExpressionTranslatorTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/AnyMethodToAggregationExpressionTranslatorTests.cs index 6580273a2f3..7e8cacaa486 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/AnyMethodToAggregationExpressionTranslatorTests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/AnyMethodToAggregationExpressionTranslatorTests.cs @@ -59,6 +59,22 @@ public void Any_with_predicate_should_work( results.Should().Equal(false, false, false, true); } + [Fact] + public void Any_on_constant_array_should_be_optimized() + { + var collection = CreateCollection(); + + var obj = new[] { 1, 2, 3 }; + var queryable = collection.AsQueryable() + .Where(x => obj.Any(y => x.Id == y)); + + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match : { _id : { $in : [1, 2, 3] } } }"); + + var results = queryable.ToList(); + results.Select(x => x.Id).Should().Equal(1, 2, 3); + } + private IMongoCollection CreateCollection() { var collection = GetCollection("test");