Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* 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 MqlMethod
{
// private static fields
private static readonly MethodInfo __exists;
private static readonly MethodInfo __isMissing;
private static readonly MethodInfo __isNullOrMissing;

// static constructor
static MqlMethod()
{
__exists = ReflectionInfo.Method((object field) => Mql.Exists(field));
__isMissing = ReflectionInfo.Method((object field) => Mql.IsMissing(field));
__isNullOrMissing = ReflectionInfo.Method((object field) => Mql.IsNullOrMissing(field));
}

// public properties
public static MethodInfo Exists => __exists;
public static MethodInfo IsMissing => __isMissing;
public static MethodInfo IsNullOrMissing => __isNullOrMissing;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ public static AggregationExpression Translate(TranslationContext context, Method
case "IndexOfAny":
return IndexOfAnyMethodToAggregationExpressionTranslator.Translate(context, expression);

case "IsMissing":
case "IsNullOrMissing":
return IsMissingMethodToAggregationExpressionTranslator.Translate(context, expression);

case "Log":
case "Log10":
return LogMethodToAggregationExpressionTranslator.Translate(context, expression);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public static AggregationExpression Translate(TranslationContext context, Method
{
var method = expression.Method;

if (method.Is(MqlMethod.Exists))
{
return IsMissingMethodToAggregationExpressionTranslator.Translate(context, expression);
}

if (method.Is(ArrayMethod.Exists) || ListMethod.IsExistsMethod(expression.Method))
{
return AnyMethodToAggregationExpressionTranslator.Translate(context, expression);
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;
using System.Linq.Expressions;
using System.Reflection;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver.Linq.Linq3Implementation.Ast;
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 IsMissingMethodToAggregationExpressionTranslator
{
private static readonly MethodInfo[] __isMissingMethods =
{
MqlMethod.Exists,
MqlMethod.IsMissing,
MqlMethod.IsNullOrMissing,
};

public static AggregationExpression Translate(TranslationContext context, MethodCallExpression expression)
{
var method = expression.Method;
var arguments = expression.Arguments;

if (method.IsOneOf(__isMissingMethods))
{
var fieldExpression = arguments[0];
var fieldTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, fieldExpression);
var fieldAst = fieldTranslation.Ast;
if (fieldAst.NodeType != AstNodeType.GetFieldExpression)
{
throw new ExpressionNotSupportedException(expression, because: $"argument to {method.Name} must be a reference to a field");
}

var ast = method.Name switch
{
nameof(Mql.Exists) => AstExpression.Ne(AstExpression.Type(fieldAst), "missing"),
nameof(Mql.IsMissing) => AstExpression.Eq(AstExpression.Type(fieldAst), "missing"),
nameof(Mql.IsNullOrMissing) => AstExpression.In(AstExpression.Type(fieldAst), new BsonArray { "null", "missing" }),
_ => throw new InvalidOperationException("Unexpected method."),
};

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

throw new ExpressionNotSupportedException(expression);
}
}
}
58 changes: 58 additions & 0 deletions src/MongoDB.Driver/Mql.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* 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;

namespace MongoDB.Driver
{
/// <summary>
/// Contains methods that can be used to access MongoDB specific functionality in LINQ queries.
/// </summary>
public static class Mql
{
/// <summary>
/// Tests whether a field exists.
/// </summary>
/// <typeparam name="TField">The type of the field.</typeparam>
/// <param name="field">The field.</param>
/// <returns><c>true</c> if the field exists.</returns>
public static bool Exists<TField>(TField field)
{
throw new NotSupportedException("This method is not functional. It is only usable in MongoDB LINQ queries.");
}

/// <summary>
/// Tests whether a field is missing.
/// </summary>
/// <typeparam name="TField">The type of the field.</typeparam>
/// <param name="field">The field.</param>
/// <returns><c>true</c> if the field is missing.</returns>
public static bool IsMissing<TField>(TField field)
{
throw new NotSupportedException("This method is not functional. It is only usable in MongoDB LINQ queries.");
}

/// <summary>
/// Tests whether a field is null or missing.
/// </summary>
/// <typeparam name="TField">The type of the field.</typeparam>
/// <param name="field">The field.</param>
/// <returns><c>true</c> if the field is null or missing.</returns>
public static bool IsNullOrMissing<TField>(TField field)
{
throw new NotSupportedException("This method is not functional. It is only usable in MongoDB LINQ queries.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/* 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.Driver.Linq;
using Xunit;

namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators
{
public class IsMissingMethodToAggregationExpressionTranslatorTests : Linq3IntegrationTest
{
[Fact]
public void Select_Exists_should_work()
{
var collection = GetCollection();

var queryable = collection.AsQueryable()
.Select(x => Mql.Exists(x.S));

var stages = Translate(collection, queryable);
AssertStages(stages, "{ $project : { _v : { $ne : [{ $type : '$S' }, 'missing'] }, _id : 0 } }");

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

[Fact]
public void Select_IsMissing_should_work()
{
var collection = GetCollection();

var queryable = collection.AsQueryable()
.Select(x => Mql.IsMissing(x.S));

var stages = Translate(collection, queryable);
AssertStages(stages, "{ $project : { _v : { $eq : [{ $type : '$S' }, 'missing'] }, _id : 0 } }");

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

[Fact]
public void Select_IsNullOrMissing_should_work()
{
var collection = GetCollection();

var queryable = collection.AsQueryable()
.Select(x => Mql.IsNullOrMissing(x.S));

var stages = Translate(collection, queryable);
AssertStages(stages, "{ $project : { _v : { $in : [{ $type : '$S' }, ['null', 'missing']] }, _id : 0 } }");

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

private IMongoCollection<C> GetCollection()
{
var collection = GetCollection<C>("test");
CreateCollection(
GetCollection<BsonDocument>("test"),
BsonDocument.Parse("{ _id : 1 }"),
BsonDocument.Parse("{ _id : 2, S : null }"),
BsonDocument.Parse("{ _id : 3, S : 'abc' }"));
return collection;
}

private class C
{
public int Id { get; set; }
public string S { get; set; }
}
}
}