Skip to content

Commit

Permalink
CSHARP-4880: Support SequenceEqual in aggregation expressions.
Browse files Browse the repository at this point in the history
  • Loading branch information
rstam committed Dec 20, 2024
1 parent 25dd800 commit fc052f4
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ internal static class QueryableMethod
private static readonly MethodInfo __selectManyWithCollectionSelectorTakingIndexAndResultSelector;
private static readonly MethodInfo __selectManyWithSelectorTakingIndex;
private static readonly MethodInfo __selectWithSelectorTakingIndex;
private static readonly MethodInfo __sequenceEqual;
private static readonly MethodInfo __single;
private static readonly MethodInfo __singleOrDefault;
private static readonly MethodInfo __singleOrDefaultWithPredicate;
Expand Down Expand Up @@ -198,6 +199,7 @@ static QueryableMethod()
__selectManyWithCollectionSelectorTakingIndexAndResultSelector = ReflectionInfo.Method((IQueryable<object> source, Expression<Func<object, int, IEnumerable<object>>> collectionSelector, Expression<Func<object, object, object>> resultSelector) => source.SelectMany(collectionSelector, resultSelector));
__selectManyWithSelectorTakingIndex = ReflectionInfo.Method((IQueryable<object> source, Expression<Func<object, int, IEnumerable<object>>> selector) => source.SelectMany(selector));
__selectWithSelectorTakingIndex = ReflectionInfo.Method((IQueryable<object> source, Expression<Func<object, int, object>> selector) => source.Select(selector));
__sequenceEqual = ReflectionInfo.Method((IQueryable<object> source1, IEnumerable<object> source2) => source1.SequenceEqual(source2));
__single = ReflectionInfo.Method((IQueryable<object> source) => source.Single());
__singleOrDefault = ReflectionInfo.Method((IQueryable<object> source) => source.SingleOrDefault());
__singleOrDefaultWithPredicate = ReflectionInfo.Method((IQueryable<object> source, Expression<Func<object, bool>> predicate) => source.SingleOrDefault(predicate));
Expand Down Expand Up @@ -302,6 +304,7 @@ static QueryableMethod()
public static MethodInfo SelectManyWithCollectionSelectorTakingIndexAndResultSelector => __selectManyWithCollectionSelectorTakingIndexAndResultSelector;
public static MethodInfo SelectManyWithSelectorTakingIndex => __selectManyWithSelectorTakingIndex;
public static MethodInfo SelectWithSelectorTakingIndex => __selectWithSelectorTakingIndex;
public static MethodInfo SequenceEqual => __sequenceEqual;
public static MethodInfo Single => __single;
public static MethodInfo SingleOrDefault => __singleOrDefault;
public static MethodInfo SingleOrDefaultWithPredicate => __singleOrDefaultWithPredicate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public static AggregationExpression Translate(TranslationContext context, Method
case "Round": return RoundMethodToAggregationExpressionTranslator.Translate(context, expression);
case "Select": return SelectMethodToAggregationExpressionTranslator.Translate(context, expression);
case "SelectMany": return SelectManyMethodToAggregationExpressionTranslator.Translate(context, expression);
case "SequenceEqual": return SequenceEqualMethodToAggregationExpressionTranslator.Translate(context, expression);
case "SetEquals": return SetEqualsMethodToAggregationExpressionTranslator.Translate(context, expression);
case "Shift": return ShiftMethodToAggregationExpressionTranslator.Translate(context, expression);
case "Split": return SplitMethodToAggregationExpressionTranslator.Translate(context, expression);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* 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.Expressions;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;

namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators
{
internal static class SequenceEqualMethodToAggregationExpressionTranslator
{
public static AggregationExpression Translate(TranslationContext context, MethodCallExpression expression)
{
var method = expression.Method;
var arguments = expression.Arguments;

if (method.IsOneOf(EnumerableMethod.SequenceEqual, QueryableMethod.SequenceEqual))
{
var firstExpression = arguments[0];
var secondExpression = arguments[1];

var firstTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, firstExpression);
var secondTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, secondExpression);

var (firstVarBinding, firstAst) = AstExpression.UseVarIfNotSimple("first", firstTranslation.Ast);
var (secondVarBinding, secondAst) = AstExpression.UseVarIfNotSimple("second", secondTranslation.Ast);
var pairVar = AstExpression.Var("pair");

var ast = AstExpression.Let(
firstVarBinding,
secondVarBinding,
@in : AstExpression.And(
AstExpression.Eq(AstExpression.Type(firstAst), "array"),
AstExpression.Eq(AstExpression.Type(secondAst), "array"),
AstExpression.Eq(AstExpression.Size(firstAst), AstExpression.Size(secondAst)),
AstExpression.AllElementsTrue(
AstExpression.Map(
input: AstExpression.Zip([firstAst, secondAst]),
@as: pairVar,
@in : AstExpression.Eq(AstExpression.ArrayElemAt(pairVar, 0), AstExpression.ArrayElemAt(pairVar, 1)))))
);

return new AggregationExpression(expression, ast, new BooleanSerializer());
}

throw new ExpressionNotSupportedException(expression);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* 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 MongoDB.Bson;
using MongoDB.TestHelpers.XunitExtensions;
using Xunit;

namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
{
public class CSharp4880Tests : Linq3IntegrationTest
{
[Theory]
[ParameterAttributeData]
public void Select_SequenceEqual_should_work(
[Values(false, true)] bool withNestedAsQueryable)
{
var collection = GetCollection();

var queryable = withNestedAsQueryable ?
collection.AsQueryable().Select(x => x.A.AsQueryable().SequenceEqual(x.B)) :
collection.AsQueryable().Select(x => x.A.SequenceEqual(x.B));

var stages = Translate(collection, queryable);
AssertStages(stages, "{ $project : { _v : { $and : [{ $eq : [{ $type : '$A' }, 'array'] }, { $eq : [{ $type : '$B' }, 'array'] }, { $eq : [{ $size : '$A' }, { $size : '$B' }] }, { $allElementsTrue : { $map : { input : { $zip : { inputs : ['$A', '$B'] } }, as : 'pair', in: { $eq : [{ $arrayElemAt : ['$$pair', 0] }, { $arrayElemAt : ['$$pair', 1] }] } } } }] }, _id : 0 } }");

var results = queryable.ToList();
results.Should().Equal(false, false, false, false, true, false, false);
}

private IMongoCollection<C> GetCollection()
{
var collection = GetCollection<C>("test");
CreateCollection(
collection.Database.GetCollection<BsonDocument>(collection.CollectionNamespace.CollectionName),
BsonDocument.Parse("{ _id : 1, A : null, B : null }"),
BsonDocument.Parse("{ _id : 2, A : null, B : [1, 2, 3] }"),
BsonDocument.Parse("{ _id : 3, A : [1, 2, 3], B : null }"),
BsonDocument.Parse("{ _id : 4, A : [1, 2, 3], B : [1, 2] }"),
BsonDocument.Parse("{ _id : 5, A : [1, 2, 3], B : [1, 2, 3] }"),
BsonDocument.Parse("{ _id : 6, A : [1, 2, 3], B : [4, 5, 6] }"),
BsonDocument.Parse("{ _id : 7, A : [1, 2, 3], B : [1, 2, 3, 4] }"));
return collection;
}

private class C
{
public int Id { get; set; }
public int[] A { get; set; }
public int[] B { get; set; }
}
}
}

0 comments on commit fc052f4

Please sign in to comment.