Skip to content

Commit

Permalink
CSHARP-3979: Support LinqExtensions.Inject in LINQ3.
Browse files Browse the repository at this point in the history
  • Loading branch information
rstam authored and BorisDog committed Dec 2, 2021
1 parent bd1e73e commit c809e8f
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ internal enum AstNodeType
ProjectStageIncludeFieldSpecification,
ProjectStageSetFieldSpecification,
RangeExpression,
RawFilter,
RedactStage,
ReduceExpression,
RegexFilterOperation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ public static AstFilter Or(params AstFilter[] filters)
return new AstOrFilter(filters);
}

public static AstRawFilter Raw(BsonDocument filter)
{
return new AstRawFilter(filter);
}

public static AstFieldOperationFilter Regex(AstFilterField field, string pattern, string options)
{
return new AstFieldOperationFilter(field, new AstRegexFilterOperation(pattern, options));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* 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.Driver.Core.Misc;
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Visitors;

namespace MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters
{
internal sealed class AstRawFilter : AstFilter
{
private readonly BsonDocument _filter;

public AstRawFilter(BsonDocument filter)
{
_filter = Ensure.IsNotNull(filter, nameof(filter));
}

public override AstNodeType NodeType => AstNodeType.RawFilter;
public BsonValue Filter => _filter;

public override AstNode Accept(AstNodeVisitor visitor)
{
return visitor.VisitRawFilter(this);
}

public override BsonValue Render()
{
return _filter;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,11 @@ public virtual AstNode VisitRangeExpression(AstRangeExpression node)
return node.Update(VisitAndConvert(node.Start), VisitAndConvert(node.End), VisitAndConvert(node.Step));
}

public virtual AstNode VisitRawFilter(AstRawFilter node)
{
return node;
}

public virtual AstNode VisitRedactStage(AstRedactStage node)
{
return node.Update(VisitAndConvert(node.Expression));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace MongoDB.Driver.Linq.Linq3Implementation.Misc
{
Expand All @@ -26,6 +27,14 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Misc
internal static class PartialEvaluator
{
#region static
private static Type[] __customLinqExtensionMethodClasses = new[]
{
typeof(DateTimeExtensions),
typeof(LinqExtensions),
typeof(MongoEnumerable),
typeof(StringExtensions)
};

public static Expression EvaluatePartially(Expression expression)
{
var nominator = new Nominator();
Expand Down Expand Up @@ -144,6 +153,24 @@ protected override Expression VisitMemberInit(MemberInitExpression node)

return node;
}

protected override Expression VisitMethodCall(MethodCallExpression node)
{
var result = base.VisitMethodCall(node);

var method = node.Method;
if (IsCustomLinqExtensionMethod(method))
{
_cannotBeEvaluated = true;
}

return result;
}

private bool IsCustomLinqExtensionMethod(MethodInfo method)
{
return __customLinqExtensionMethodClasses.Contains(method.DeclaringType);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* 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.Reflection;

namespace MongoDB.Driver.Linq.Linq3Implementation.Reflection
{
internal static class LinqExtensionsMethod
{
// private static fields
private static readonly MethodInfo __inject;

// static constructor
static LinqExtensionsMethod()
{
__inject = ReflectionInfo.Method((FilterDefinition<object> filter) => filter.Inject());
}

// public properties
public static MethodInfo Inject => __inject;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* 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 System.Linq.Expressions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;

namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.MethodTranslators
{
internal static class InjectMethodToFilterTranslator
{
// public static methods
public static AstFilter Translate(TranslationContext context, MethodCallExpression expression)
{
var method = expression.Method;
var arguments = expression.Arguments;

if (method.Is(LinqExtensionsMethod.Inject))
{
var filterExpression = arguments[0];
var filterDefinition = filterExpression.GetConstantValue<object>(expression);
var filterDefinitionType = filterDefinition.GetType(); // we KNOW it's a FilterDefinition<TDocument> because of the Inject method signature
var documentType = filterDefinitionType.GetGenericArguments()[0];
var serializerRegistry = BsonSerializer.SerializerRegistry;
var documentSerializer = serializerRegistry.GetSerializer(documentType); // TODO: is this the right serializer?
var renderMethodArgumentTypes = new[]
{
typeof(IBsonSerializer<>).MakeGenericType(documentType),
typeof(IBsonSerializerRegistry),
typeof(LinqProvider)
};
var renderMethod = filterDefinitionType.GetMethod("Render", renderMethodArgumentTypes);
var renderedFilter = (BsonDocument)renderMethod.Invoke(filterDefinition, new object[] { documentSerializer, serializerRegistry, LinqProvider.V3 });
return AstFilter.Raw(renderedFilter);
}

throw new ExpressionNotSupportedException(expression);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static AstFilter Translate(TranslationContext context, MethodCallExpressi
case "EndsWith": return EndsWithMethodToFilterTranslator.Translate(context, expression);
case "Equals": return EqualsMethodToFilterTranslator.Translate(context, expression);
case "HasFlag": return HasFlagMethodToFilterTranslator.Translate(context, expression);
case "Inject": return InjectMethodToFilterTranslator.Translate(context, expression);
case "IsMatch": return IsMatchMethodToFilterTranslator.Translate(context, expression);
case "IsNullOrEmpty": return IsNullOrEmptyMethodToFilterTranslator.Translate(context, expression);
case "StartsWith": return StartsWithMethodToFilterTranslator.Translate(context, expression);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1115,27 +1115,27 @@ public void Binding_through_an_unnecessary_conversion_with_a_builder()
root.A.Should().Be("Awesome");
}

//[Fact]
//public void Injecting_a_filter()
//{
// var filter = Builders<Root>.Filter.Eq(x => x.B, "Balloon");
// var root = __collection.FindSync(x => filter.Inject()).Single();

// root.Should().NotBeNull();
// root.A.Should().Be("Awesome");
// root.B.Should().Be("Balloon");
//}

//[Fact]
//public void Injecting_a_filter_with_a_conjunction()
//{
// var filter = Builders<Root>.Filter.Eq(x => x.B, "Balloon");
// var root = __collection.FindSync(x => x.A == "Awesome" && filter.Inject()).Single();

// root.Should().NotBeNull();
// root.A.Should().Be("Awesome");
// root.B.Should().Be("Balloon");
//}
[Fact]
public void Injecting_a_filter()
{
var filter = Builders<Root>.Filter.Eq(x => x.B, "Balloon");
var root = __collection.FindSync(x => filter.Inject()).Single();

root.Should().NotBeNull();
root.A.Should().Be("Awesome");
root.B.Should().Be("Balloon");
}

[Fact]
public void Injecting_a_filter_with_a_conjunction()
{
var filter = Builders<Root>.Filter.Eq(x => x.B, "Balloon");
var root = __collection.FindSync(x => x.A == "Awesome" && filter.Inject()).Single();

root.Should().NotBeNull();
root.A.Should().Be("Awesome");
root.B.Should().Be("Balloon");
}

private TDocument FindFirstOrDefault<TDocument>(IMongoCollection<TDocument> collection, int id) where TDocument : IRoot
{
Expand Down
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 MongoDB.Driver.Linq;
using Xunit;

namespace MongoDB.Driver.Tests.Linq.Linq3ImplementationTests.Jira
{
public class CSharp3979Tests : Linq3IntegrationTest
{
[Fact]
public void Inject_should_work()
{
var client = DriverTestConfiguration.Linq3Client;
var database = client.GetDatabase("test");
var collection = database.GetCollection<C>("test");

var filter = Builders<C>.Filter.Eq(c => c.X, 1);
var queryable = collection.AsQueryable().Where(x => filter.Inject());

var stages = Translate(collection, queryable);
var expectedStages = new[]
{
"{ $match : { X : 1 } }"
};
AssertStages(stages, expectedStages);
}

[Fact]
public void Inject_embedded_in_an_expression_should_work()
{
var client = DriverTestConfiguration.Linq3Client;
var database = client.GetDatabase("test");
var collection = database.GetCollection<C>("test");

var filter = Builders<C>.Filter.Lte(c => c.X, 10);
var queryable = collection.AsQueryable().Where(x => x.X >= 1 && filter.Inject());

var stages = Translate(collection, queryable);
var expectedStages = new[]
{
"{ $match : { $and : [{ X : { $gte : 1 } }, { X : { $lte : 10 } }] } }"
};
AssertStages(stages, expectedStages);
}

private class C
{
public int Id { get; set; }
public int X { get; set; }
}
}
}

0 comments on commit c809e8f

Please sign in to comment.