Skip to content

Commit 1b1934d

Browse files
committed
CSHARP-4858: Add IsMissing and IsNullOrMissing MongoDBFunctions.
1 parent 2902e9c commit 1b1934d

File tree

6 files changed

+261
-0
lines changed

6 files changed

+261
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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 MqlMethod
21+
{
22+
// private static fields
23+
private static readonly MethodInfo __exists;
24+
private static readonly MethodInfo __isMissing;
25+
private static readonly MethodInfo __isNullOrMissing;
26+
27+
// static constructor
28+
static MqlMethod()
29+
{
30+
__exists = ReflectionInfo.Method((object field) => Mql.Exists(field));
31+
__isMissing = ReflectionInfo.Method((object field) => Mql.IsMissing(field));
32+
__isNullOrMissing = ReflectionInfo.Method((object field) => Mql.IsNullOrMissing(field));
33+
}
34+
35+
// public properties
36+
public static MethodInfo Exists => __exists;
37+
public static MethodInfo IsMissing => __isMissing;
38+
public static MethodInfo IsNullOrMissing => __isNullOrMissing;
39+
}
40+
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodCallExpressionToAggregationExpressionTranslator.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ public static AggregationExpression Translate(TranslationContext context, Method
138138
case "IndexOfAny":
139139
return IndexOfAnyMethodToAggregationExpressionTranslator.Translate(context, expression);
140140

141+
case "IsMissing":
142+
case "IsNullOrMissing":
143+
return FieldExistsOrIsMissingMethodToAggregationExpressionTranslator.Translate(context, expression);
144+
141145
case "Log":
142146
case "Log10":
143147
return LogMethodToAggregationExpressionTranslator.Translate(context, expression);

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ExistsMethodToAggregationExpressionExpression.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ public static AggregationExpression Translate(TranslationContext context, Method
2525
{
2626
var method = expression.Method;
2727

28+
if (method.Is(MqlMethod.Exists))
29+
{
30+
return FieldExistsOrIsMissingMethodToAggregationExpressionTranslator.Translate(context, expression);
31+
}
32+
2833
if (method.Is(ArrayMethod.Exists) || ListMethod.IsExistsMethod(expression.Method))
2934
{
3035
return AnyMethodToAggregationExpressionTranslator.Translate(context, expression);
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;
17+
using System.Linq.Expressions;
18+
using System.Reflection;
19+
using MongoDB.Bson;
20+
using MongoDB.Bson.Serialization.Serializers;
21+
using MongoDB.Driver.Linq.Linq3Implementation.Ast;
22+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
23+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
24+
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
25+
26+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators
27+
{
28+
internal static class FieldExistsOrIsMissingMethodToAggregationExpressionTranslator
29+
{
30+
private static readonly MethodInfo[] __isMissingMethods =
31+
{
32+
MqlMethod.Exists,
33+
MqlMethod.IsMissing,
34+
MqlMethod.IsNullOrMissing,
35+
};
36+
37+
public static AggregationExpression Translate(TranslationContext context, MethodCallExpression expression)
38+
{
39+
var method = expression.Method;
40+
var arguments = expression.Arguments;
41+
42+
if (method.IsOneOf(__isMissingMethods))
43+
{
44+
var fieldExpression = arguments[0];
45+
var fieldTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, fieldExpression);
46+
var fieldAst = fieldTranslation.Ast;
47+
if (fieldAst.NodeType != AstNodeType.GetFieldExpression)
48+
{
49+
throw new ExpressionNotSupportedException(expression, because: $"argument to {method.Name} must be a reference to a field");
50+
}
51+
52+
var ast = method.Name switch
53+
{
54+
nameof(Mql.Exists) => AstExpression.Ne(AstExpression.Type(fieldAst), "missing"),
55+
nameof(Mql.IsMissing) => AstExpression.Eq(AstExpression.Type(fieldAst), "missing"),
56+
nameof(Mql.IsNullOrMissing) => AstExpression.In(AstExpression.Type(fieldAst), new BsonArray { "null", "missing" }),
57+
_ => throw new InvalidOperationException("Unexpected method."),
58+
};
59+
60+
return new AggregationExpression(expression, ast, BooleanSerializer.Instance);
61+
}
62+
63+
throw new ExpressionNotSupportedException(expression);
64+
}
65+
}
66+
}

src/MongoDB.Driver/Mql.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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;
17+
18+
namespace MongoDB.Driver
19+
{
20+
/// <summary>
21+
/// Contains methods that can be used to access MongoDB specific functionality in LINQ queries.
22+
/// </summary>
23+
public static class Mql
24+
{
25+
/// <summary>
26+
/// Tests whether a field exists.
27+
/// </summary>
28+
/// <typeparam name="TField">The type of the field.</typeparam>
29+
/// <param name="field">The field.</param>
30+
/// <returns><c>true</c> if the field exists.</returns>
31+
public static bool Exists<TField>(TField field)
32+
{
33+
throw new NotSupportedException("This method is not functional. It is only usable in MongoDB LINQ queries.");
34+
}
35+
36+
/// <summary>
37+
/// Tests whether a field is missing.
38+
/// </summary>
39+
/// <typeparam name="TField">The type of the field.</typeparam>
40+
/// <param name="field">The field.</param>
41+
/// <returns><c>true</c> if the field is missing.</returns>
42+
public static bool IsMissing<TField>(TField field)
43+
{
44+
throw new NotSupportedException("This method is not functional. It is only usable in MongoDB LINQ queries.");
45+
}
46+
47+
/// <summary>
48+
/// Tests whether a field is null or missing.
49+
/// </summary>
50+
/// <typeparam name="TField">The type of the field.</typeparam>
51+
/// <param name="field">The field.</param>
52+
/// <returns><c>true</c> if the field is null or missing.</returns>
53+
public static bool IsNullOrMissing<TField>(TField field)
54+
{
55+
throw new NotSupportedException("This method is not functional. It is only usable in MongoDB LINQ queries.");
56+
}
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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 FluentAssertions;
18+
using MongoDB.Bson;
19+
using MongoDB.Driver.Linq;
20+
using Xunit;
21+
22+
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators
23+
{
24+
public class FieldExistsOrIsMissingMethodToAggregationExpressionTranslatorTests : Linq3IntegrationTest
25+
{
26+
[Fact]
27+
public void Select_Exists_should_work()
28+
{
29+
var collection = GetCollection();
30+
31+
var queryable = collection.AsQueryable()
32+
.Select(x => Mql.Exists(x.S));
33+
34+
var stages = Translate(collection, queryable);
35+
AssertStages(stages, "{ $project : { _v : { $ne : [{ $type : '$S' }, 'missing'] }, _id : 0 } }");
36+
37+
var results = queryable.ToList();
38+
results.Should().Equal(false, true, true);
39+
}
40+
41+
[Fact]
42+
public void Select_IsMissing_should_work()
43+
{
44+
var collection = GetCollection();
45+
46+
var queryable = collection.AsQueryable()
47+
.Select(x => Mql.IsMissing(x.S));
48+
49+
var stages = Translate(collection, queryable);
50+
AssertStages(stages, "{ $project : { _v : { $eq : [{ $type : '$S' }, 'missing'] }, _id : 0 } }");
51+
52+
var results = queryable.ToList();
53+
results.Should().Equal(true, false, false);
54+
}
55+
56+
[Fact]
57+
public void Select_IsNullOrMissing_should_work()
58+
{
59+
var collection = GetCollection();
60+
61+
var queryable = collection.AsQueryable()
62+
.Select(x => Mql.IsNullOrMissing(x.S));
63+
64+
var stages = Translate(collection, queryable);
65+
AssertStages(stages, "{ $project : { _v : { $in : [{ $type : '$S' }, ['null', 'missing']] }, _id : 0 } }");
66+
67+
var results = queryable.ToList();
68+
results.Should().Equal(true, true, false);
69+
}
70+
71+
private IMongoCollection<C> GetCollection()
72+
{
73+
var collection = GetCollection<C>("test");
74+
CreateCollection(
75+
GetCollection<BsonDocument>("test"),
76+
BsonDocument.Parse("{ _id : 1 }"),
77+
BsonDocument.Parse("{ _id : 2, S : null }"),
78+
BsonDocument.Parse("{ _id : 3, S : 'abc' }"));
79+
return collection;
80+
}
81+
82+
private class C
83+
{
84+
public int Id { get; set; }
85+
public string S { get; set; }
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)