Skip to content

Commit cc9ba1b

Browse files
CSHARP-1356: Fix Any workflow with Contains method.
1 parent c0ba46c commit cc9ba1b

File tree

4 files changed

+127
-12
lines changed

4 files changed

+127
-12
lines changed

src/MongoDB.Driver/FilterDefinitionBuilder.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,8 +1839,15 @@ public override BsonDocument Render(IBsonSerializer<TDocument> documentSerialize
18391839

18401840
private static BsonDocument NegateArbitraryFilter(BsonDocument filter)
18411841
{
1842-
// $not only works as a meta operator on a single operator so simulate Not using $nor
1843-
return new BsonDocument("$nor", new BsonArray { filter });
1842+
if (filter.ElementCount == 1 && filter.GetElement(0).Name.StartsWith("$"))
1843+
{
1844+
return new BsonDocument("$not", filter);
1845+
}
1846+
else
1847+
{
1848+
// $not only works as a meta operator on a single operator so simulate Not using $nor
1849+
return new BsonDocument("$nor", new BsonArray { filter });
1850+
}
18441851
}
18451852

18461853
private static BsonDocument NegateSingleElementFilter(BsonDocument filter, BsonElement element)
@@ -1898,6 +1905,8 @@ private static BsonDocument NegateSingleElementTopLevelOperatorFilter(BsonDocume
18981905
{
18991906
switch (element.Name)
19001907
{
1908+
case "$and":
1909+
return new BsonDocument("$nor", new BsonArray { filter });
19011910
case "$or":
19021911
return new BsonDocument("$nor", element.Value);
19031912
case "$nor":

src/MongoDB.Driver/Linq/Translators/PredicateTranslator.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,11 @@ private bool CanAnyBeRenderedWithoutElemMatch(Expression node)
202202
var pipelineExpression = node as PipelineExpression;
203203
if (pipelineExpression != null)
204204
{
205+
if (pipelineExpression.ResultOperator is ContainsResultOperator)
206+
{
207+
return false;
208+
}
209+
205210
var source = pipelineExpression.Source as ISerializationExpression;
206211
return source == null;
207212
}
@@ -992,7 +997,14 @@ private FilterDefinition<BsonDocument> TranslatePipelineContains(PipelineExpress
992997
var ienumerableInterfaceType = constantExpression.Type.FindIEnumerable();
993998
var itemType = ienumerableInterfaceType.GetTypeInfo().GetGenericArguments()[0];
994999
var serializedValues = field.SerializeValues(itemType, (IEnumerable)constantExpression.Value);
995-
return __builder.In(field.FieldName, serializedValues);
1000+
if (string.IsNullOrEmpty(field.FieldName))
1001+
{
1002+
return new BsonDocument("$in", serializedValues);
1003+
}
1004+
else
1005+
{
1006+
return __builder.In(field.FieldName, serializedValues);
1007+
}
9961008
}
9971009
}
9981010
else
@@ -1654,11 +1666,6 @@ protected internal override Expression VisitDocument(DocumentExpression node)
16541666
{
16551667
return new FieldExpression("", node.Serializer);
16561668
}
1657-
1658-
protected internal override Expression VisitPipeline(PipelineExpression node)
1659-
{
1660-
return node;
1661-
}
16621669
}
16631670
}
16641671
}

tests/MongoDB.Driver.Tests/Linq/IntegrationTestBase.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,58 @@
1616
using System;
1717
using System.Collections.Concurrent;
1818
using System.Collections.Generic;
19+
using System.Linq;
1920
using MongoDB.Bson.Serialization.Attributes;
20-
using Xunit;
2121

2222
namespace MongoDB.Driver.Tests.Linq
2323
{
2424
public abstract class IntegrationTestBase
2525
{
2626
protected static IMongoCollection<Root> __collection;
2727
protected static IMongoCollection<Other> __otherCollection;
28+
protected static IMongoCollection<Root> __customCollection;
29+
protected static List<Root> __customDocuments;
30+
2831
private static ConcurrentDictionary<Type, bool> __oneTimeSetupTracker = new ConcurrentDictionary<Type, bool>();
2932

3033
protected IntegrationTestBase()
3134
{
3235
__oneTimeSetupTracker.GetOrAdd(GetType(), OneTimeSetup); // run OneTimeSetup once per subclass
3336
}
3437

38+
protected virtual void FillCustomDocuments(List<Root> customDocuments)
39+
{
40+
}
41+
3542
private bool OneTimeSetup(Type type)
3643
{
3744
var client = DriverTestConfiguration.Client;
3845
var db = client.GetDatabase(DriverTestConfiguration.DatabaseNamespace.DatabaseName);
3946
__collection = db.GetCollection<Root>(DriverTestConfiguration.CollectionNamespace.CollectionName);
4047
__otherCollection = db.GetCollection<Other>(DriverTestConfiguration.CollectionNamespace.CollectionName + "_other");
48+
__customCollection = db.GetCollection<Root>(DriverTestConfiguration.CollectionNamespace.CollectionName + "_custom");
4149
db.DropCollection(__collection.CollectionNamespace.CollectionName);
4250
db.DropCollection(__collection.CollectionNamespace.CollectionName + "_other");
4351

4452
InsertFirst();
4553
InsertSecond();
4654
InsertJoin();
55+
ConfigureCustomCollection(db);
4756

4857
return true;
4958
}
5059

60+
private void ConfigureCustomCollection(IMongoDatabase db)
61+
{
62+
__customDocuments = new List<Root>();
63+
FillCustomDocuments(__customDocuments);
64+
db.DropCollection(__customCollection.CollectionNamespace.CollectionName);
65+
if (__customDocuments.Count > 0)
66+
{
67+
__customCollection.InsertMany(__customDocuments);
68+
}
69+
}
70+
5171
private void InsertFirst()
5272
{
5373
var root = new Root

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

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,48 @@ public void Any_with_a_nested_Any()
213213
"{ G : { $elemMatch : { D : \"Don't\", \"S\" : { $elemMatch : { E : null, D : \"Delilah\" } } } } }");
214214
}
215215

216+
[Fact]
217+
public void Any_with_a_not_and_a_predicate_with_not_contains()
218+
{
219+
var x = new[] { 1, 2 };
220+
221+
AssertUsingCustomCollection(
222+
c => !c.M.Any(a => !x.Contains(a)),
223+
"{ M : { '$not' : { '$elemMatch' : { '$not' : { '$in' : [1, 2] } } } } }");
224+
}
225+
226+
[Fact]
227+
public void Any_with_a_not_and_a_predicate_with_contains()
228+
{
229+
var x = new[] { 1, 2 };
230+
231+
AssertUsingCustomCollection(
232+
c => !c.M.Any(a => x.Contains(a)),
233+
"{ M : { '$not' : { '$elemMatch' : { '$in' : [1, 2] } } } }");
234+
}
235+
236+
[Fact]
237+
public void Any_with_a_predicate_with_contains()
238+
{
239+
var x = new[] { 1, 2 };
240+
241+
AssertUsingCustomCollection(
242+
c => c.M.Any(a => x.Contains(a)),
243+
"{ M : { '$elemMatch' : { '$in' : [1, 2] } } }"
244+
);
245+
}
246+
247+
[Fact]
248+
public void Any_with_a_predicate_with_not_contains()
249+
{
250+
var x = new[] { 1, 2 };
251+
252+
AssertUsingCustomCollection(
253+
c => c.M.Any(a => !x.Contains(a)),
254+
"{ M : { '$elemMatch' : { '$not' : { '$in' : [1, 2] } } } }"
255+
);
256+
}
257+
216258
[Fact]
217259
public void Any_with_a_not()
218260
{
@@ -251,7 +293,7 @@ public void Any_with_a_predicate_on_scalars()
251293
1,
252294
"{\"C.E.I\": /^ick/s}");
253295

254-
// this isn't a legal query, as in, there isn't any
296+
// this isn't a legal query, as in, there isn't any
255297
// way to render this legally for the server...
256298
//Assert(
257299
// x => x.C.E.I.Any(i => i.StartsWith("ick") && i == "Jack"),
@@ -284,7 +326,7 @@ public void Any_with_local_contains_on_an_embedded_document()
284326
Assert(
285327
x => x.G.Any(g => local.Contains(g.D)),
286328
1,
287-
"{\"G.D\": { $in: [\"Delilah\", \"Dolphin\" ] } }");
329+
"{ 'G' : { '$elemMatch' : { 'D' : { $in : ['Delilah', 'Dolphin'] } } } }");
288330
}
289331

290332
[Fact]
@@ -910,7 +952,7 @@ public void Assert<T>(IMongoCollection<T> collection, Expression<Func<T, bool>>
910952
Assert(collection, filter, expectedCount, BsonDocument.Parse(expectedFilter));
911953
}
912954

913-
public void Assert<T>(IMongoCollection<T> collection, Expression<Func<T, bool>> filter, int expectedCount, BsonDocument expectedFilter)
955+
public List<T> Assert<T>(IMongoCollection<T> collection, Expression<Func<T, bool>> filter, int expectedCount, BsonDocument expectedFilter)
914956
{
915957
var serializer = BsonSerializer.SerializerRegistry.GetSerializer<T>();
916958
var filterDocument = PredicateTranslator.Translate(filter, serializer, BsonSerializer.SerializerRegistry);
@@ -919,6 +961,7 @@ public void Assert<T>(IMongoCollection<T> collection, Expression<Func<T, bool>>
919961

920962
filterDocument.Should().Be(expectedFilter);
921963
list.Count.Should().Be(expectedCount);
964+
return list;
922965
}
923966

924967
public void Assert(Expression<Func<Root, bool>> filter, int expectedCount, string expectedFilter)
@@ -930,5 +973,41 @@ public void Assert(Expression<Func<Root, bool>> filter, int expectedCount, BsonD
930973
{
931974
Assert(__collection, filter, expectedCount, expectedFilter);
932975
}
976+
977+
protected override void FillCustomDocuments(List<Root> customDocuments)
978+
{
979+
customDocuments.AddRange(
980+
new[]
981+
{
982+
new Root { Id = 1, M = new int[0] },
983+
new Root { Id = 2, M = new [] { 1 } },
984+
new Root { Id = 3, M = new [] { 2 } },
985+
new Root { Id = 4, M = new [] { 3 } },
986+
new Root { Id = 5, M = new [] { 4 } },
987+
new Root { Id = 6, M = new [] { 1, 2 } },
988+
new Root { Id = 7, M = new [] { 1, 3 } },
989+
new Root { Id = 8, M = new [] { 1, 4 } },
990+
new Root { Id = 9, M = new [] { 2, 3 } },
991+
new Root { Id = 10, M = new [] { 3, 4} },
992+
new Root { Id = 11, M = new [] { 1, 2, 3 } },
993+
new Root { Id = 12, M = new [] { 1, 2 ,4 } },
994+
new Root { Id = 13, M = new [] { 1, 3, 4 } },
995+
new Root { Id = 14, M = new [] { 1, 2, 3, 4 } }
996+
});
997+
}
998+
999+
public void AssertUsingCustomCollection(Expression<Func<Root, bool>> filter, string expectedFilter)
1000+
{
1001+
AssertUsingCustomCollection(filter, BsonDocument.Parse(expectedFilter));
1002+
}
1003+
1004+
public void AssertUsingCustomCollection(Expression<Func<Root, bool>> filter, BsonDocument expectedFilter)
1005+
{
1006+
var expectedResult = __customDocuments.Where(filter.Compile()).ToList();
1007+
1008+
var queryResult = Assert(__customCollection, filter, expectedResult.Count, expectedFilter);
1009+
1010+
queryResult.Select(r => r.Id).Should().Equal(expectedResult.Select(r => r.Id));
1011+
}
9331012
}
9341013
}

0 commit comments

Comments
 (0)