Skip to content

Commit 8da3db7

Browse files
committed
CSHARP-3979: Support LinqExtensions.Inject in LINQ3.
1 parent f4fd11d commit 8da3db7

File tree

10 files changed

+261
-21
lines changed

10 files changed

+261
-21
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/AstNodeType.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ internal enum AstNodeType
107107
ProjectStageIncludeFieldSpecification,
108108
ProjectStageSetFieldSpecification,
109109
RangeExpression,
110+
RawFilter,
110111
RedactStage,
111112
ReduceExpression,
112113
RegexFilterOperation,

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstFilter.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@ public static AstFilter Or(params AstFilter[] filters)
211211
return new AstOrFilter(filters);
212212
}
213213

214+
public static AstRawFilter Raw(BsonDocument filter)
215+
{
216+
return new AstRawFilter(filter);
217+
}
218+
214219
public static AstFieldOperationFilter Regex(AstFilterField field, string pattern, string options)
215220
{
216221
return new AstFieldOperationFilter(field, new AstRegexFilterOperation(pattern, options));
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using MongoDB.Bson;
17+
using MongoDB.Driver.Core.Misc;
18+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Visitors;
19+
20+
namespace MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters
21+
{
22+
internal sealed class AstRawFilter : AstFilter
23+
{
24+
private readonly BsonDocument _filter;
25+
26+
public AstRawFilter(BsonDocument filter)
27+
{
28+
_filter = Ensure.IsNotNull(filter, nameof(filter));
29+
}
30+
31+
public override AstNodeType NodeType => AstNodeType.RawFilter;
32+
public BsonValue Filter => _filter;
33+
34+
public override AstNode Accept(AstNodeVisitor visitor)
35+
{
36+
return visitor.VisitRawFilter(this);
37+
}
38+
39+
public override BsonValue Render()
40+
{
41+
return _filter;
42+
}
43+
}
44+
}

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Visitors/AstNodeVisitor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,11 @@ public virtual AstNode VisitRangeExpression(AstRangeExpression node)
569569
return node.Update(VisitAndConvert(node.Start), VisitAndConvert(node.End), VisitAndConvert(node.Step));
570570
}
571571

572+
public virtual AstNode VisitRawFilter(AstRawFilter node)
573+
{
574+
return node;
575+
}
576+
572577
public virtual AstNode VisitRedactStage(AstRedactStage node)
573578
{
574579
return node.Update(VisitAndConvert(node.Expression));

src/MongoDB.Driver/Linq/Linq3Implementation/Misc/PartialEvaluator.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using System.Collections.Generic;
1818
using System.Linq;
1919
using System.Linq.Expressions;
20+
using System.Reflection;
2021

2122
namespace MongoDB.Driver.Linq.Linq3Implementation.Misc
2223
{
@@ -26,6 +27,14 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Misc
2627
internal static class PartialEvaluator
2728
{
2829
#region static
30+
private static Type[] __customLinqExtensionMethodClasses = new[]
31+
{
32+
typeof(DateTimeExtensions),
33+
typeof(LinqExtensions),
34+
typeof(MongoEnumerable),
35+
typeof(StringExtensions)
36+
};
37+
2938
public static Expression EvaluatePartially(Expression expression)
3039
{
3140
var nominator = new Nominator();
@@ -144,6 +153,24 @@ protected override Expression VisitMemberInit(MemberInitExpression node)
144153

145154
return node;
146155
}
156+
157+
protected override Expression VisitMethodCall(MethodCallExpression node)
158+
{
159+
var result = base.VisitMethodCall(node);
160+
161+
var method = node.Method;
162+
if (IsCustomLinqExtensionMethod(method))
163+
{
164+
_cannotBeEvaluated = true;
165+
}
166+
167+
return result;
168+
}
169+
170+
private bool IsCustomLinqExtensionMethod(MethodInfo method)
171+
{
172+
return __customLinqExtensionMethodClasses.Contains(method.DeclaringType);
173+
}
147174
}
148175
}
149176
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System.Reflection;
17+
18+
namespace MongoDB.Driver.Linq.Linq3Implementation.Reflection
19+
{
20+
internal static class LinqExtensionsMethod
21+
{
22+
// private static fields
23+
private static readonly MethodInfo __inject;
24+
25+
// static constructor
26+
static LinqExtensionsMethod()
27+
{
28+
__inject = ReflectionInfo.Method((FilterDefinition<object> filter) => filter.Inject());
29+
}
30+
31+
// public properties
32+
public static MethodInfo Inject => __inject;
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System.Linq;
17+
using System.Linq.Expressions;
18+
using MongoDB.Bson;
19+
using MongoDB.Bson.Serialization;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
21+
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
22+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
23+
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
24+
25+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.MethodTranslators
26+
{
27+
internal static class InjectMethodToFilterTranslator
28+
{
29+
// public static methods
30+
public static AstFilter Translate(TranslationContext context, MethodCallExpression expression)
31+
{
32+
var method = expression.Method;
33+
var arguments = expression.Arguments;
34+
35+
if (method.Is(LinqExtensionsMethod.Inject))
36+
{
37+
var filterExpression = arguments[0];
38+
var filterDefinition = filterExpression.GetConstantValue<object>(expression);
39+
var filterDefinitionType = filterDefinition.GetType(); // we KNOW it's a FilterDefinition<TDocument> because of the Inject method signature
40+
var documentType = filterDefinitionType.GetGenericArguments()[0];
41+
var serializerRegistry = BsonSerializer.SerializerRegistry;
42+
var documentSerializer = serializerRegistry.GetSerializer(documentType); // TODO: is this the right serializer?
43+
var renderMethodArgumentTypes = new[]
44+
{
45+
typeof(IBsonSerializer<>).MakeGenericType(documentType),
46+
typeof(IBsonSerializerRegistry),
47+
typeof(LinqProvider)
48+
};
49+
var renderMethod = filterDefinitionType.GetMethod("Render", renderMethodArgumentTypes);
50+
var renderedFilter = (BsonDocument)renderMethod.Invoke(filterDefinition, new object[] { documentSerializer, serializerRegistry, LinqProvider.V3 });
51+
return AstFilter.Raw(renderedFilter);
52+
}
53+
54+
throw new ExpressionNotSupportedException(expression);
55+
}
56+
}
57+
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/MethodCallExpressionToFilterTranslator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public static AstFilter Translate(TranslationContext context, MethodCallExpressi
2828
case "EndsWith": return EndsWithMethodToFilterTranslator.Translate(context, expression);
2929
case "Equals": return EqualsMethodToFilterTranslator.Translate(context, expression);
3030
case "HasFlag": return HasFlagMethodToFilterTranslator.Translate(context, expression);
31+
case "Inject": return InjectMethodToFilterTranslator.Translate(context, expression);
3132
case "IsMatch": return IsMatchMethodToFilterTranslator.Translate(context, expression);
3233
case "IsNullOrEmpty": return IsNullOrEmptyMethodToFilterTranslator.Translate(context, expression);
3334
case "StartsWith": return StartsWithMethodToFilterTranslator.Translate(context, expression);

tests/MongoDB.Driver.Tests/Linq/Linq2ImplementationTestsOnLinq3/Translators/PredicateTranslatorTests.cs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,27 +1115,27 @@ public void Binding_through_an_unnecessary_conversion_with_a_builder()
11151115
root.A.Should().Be("Awesome");
11161116
}
11171117

1118-
//[Fact]
1119-
//public void Injecting_a_filter()
1120-
//{
1121-
// var filter = Builders<Root>.Filter.Eq(x => x.B, "Balloon");
1122-
// var root = __collection.FindSync(x => filter.Inject()).Single();
1123-
1124-
// root.Should().NotBeNull();
1125-
// root.A.Should().Be("Awesome");
1126-
// root.B.Should().Be("Balloon");
1127-
//}
1128-
1129-
//[Fact]
1130-
//public void Injecting_a_filter_with_a_conjunction()
1131-
//{
1132-
// var filter = Builders<Root>.Filter.Eq(x => x.B, "Balloon");
1133-
// var root = __collection.FindSync(x => x.A == "Awesome" && filter.Inject()).Single();
1134-
1135-
// root.Should().NotBeNull();
1136-
// root.A.Should().Be("Awesome");
1137-
// root.B.Should().Be("Balloon");
1138-
//}
1118+
[Fact]
1119+
public void Injecting_a_filter()
1120+
{
1121+
var filter = Builders<Root>.Filter.Eq(x => x.B, "Balloon");
1122+
var root = __collection.FindSync(x => filter.Inject()).Single();
1123+
1124+
root.Should().NotBeNull();
1125+
root.A.Should().Be("Awesome");
1126+
root.B.Should().Be("Balloon");
1127+
}
1128+
1129+
[Fact]
1130+
public void Injecting_a_filter_with_a_conjunction()
1131+
{
1132+
var filter = Builders<Root>.Filter.Eq(x => x.B, "Balloon");
1133+
var root = __collection.FindSync(x => x.A == "Awesome" && filter.Inject()).Single();
1134+
1135+
root.Should().NotBeNull();
1136+
root.A.Should().Be("Awesome");
1137+
root.B.Should().Be("Balloon");
1138+
}
11391139

11401140
private TDocument FindFirstOrDefault<TDocument>(IMongoCollection<TDocument> collection, int id) where TDocument : IRoot
11411141
{
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System.Linq;
17+
using MongoDB.Driver.Linq;
18+
using Xunit;
19+
20+
namespace MongoDB.Driver.Tests.Linq.Linq3ImplementationTests.Jira
21+
{
22+
public class CSharp3979Tests : Linq3IntegrationTest
23+
{
24+
[Fact]
25+
public void Inject_should_work()
26+
{
27+
var client = DriverTestConfiguration.Linq3Client;
28+
var database = client.GetDatabase("test");
29+
var collection = database.GetCollection<C>("test");
30+
31+
var filter = Builders<C>.Filter.Eq(c => c.X, 1);
32+
var queryable = collection.AsQueryable().Where(x => filter.Inject());
33+
34+
var stages = Translate(collection, queryable);
35+
var expectedStages = new[]
36+
{
37+
"{ $match : { X : 1 } }"
38+
};
39+
AssertStages(stages, expectedStages);
40+
}
41+
42+
[Fact]
43+
public void Inject_embedded_in_an_expression_should_work()
44+
{
45+
var client = DriverTestConfiguration.Linq3Client;
46+
var database = client.GetDatabase("test");
47+
var collection = database.GetCollection<C>("test");
48+
49+
var filter = Builders<C>.Filter.Lte(c => c.X, 10);
50+
var queryable = collection.AsQueryable().Where(x => x.X >= 1 && filter.Inject());
51+
52+
var stages = Translate(collection, queryable);
53+
var expectedStages = new[]
54+
{
55+
"{ $match : { $and : [{ X : { $gte : 1 } }, { X : { $lte : 10 } }] } }"
56+
};
57+
AssertStages(stages, expectedStages);
58+
}
59+
60+
private class C
61+
{
62+
public int Id { get; set; }
63+
public int X { get; set; }
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)