Skip to content

Commit ca00451

Browse files
author
rstam
committed
Finished implementing CSHARP-433. Implemented using == to compare to a type. Modified Query.Or to handle { } (an empty query matches all document). Use { } instead of nasty $mod trick to match all documents.
1 parent 8bf97a8 commit ca00451

File tree

6 files changed

+319
-6
lines changed

6 files changed

+319
-6
lines changed

Driver/Builders/QueryBuilder.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,12 +572,18 @@ public static QueryComplete Or(params IMongoQuery[] queries)
572572
}
573573
else
574574
{
575-
queryArray.Add(queryDocument);
575+
// skip query like { } which matches everything
576+
if (queryDocument.ElementCount != 0)
577+
{
578+
queryArray.Add(queryDocument);
579+
}
576580
}
577581
}
578582

579583
switch (queryArray.Count)
580584
{
585+
case 0:
586+
return new QueryComplete(new QueryDocument()); // all queries were empty queries so just return an empty query
581587
case 1:
582588
return new QueryComplete(queryArray[0].AsBsonDocument);
583589
default:

Driver/Linq/Expressions/ExpressionFormatter.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,13 @@ private void VisitValue(object value)
513513
return;
514514
}
515515

516+
var type = value as Type;
517+
if (type != null)
518+
{
519+
_sb.AppendFormat("typeof({0})", FriendlyClassName(type));
520+
return;
521+
}
522+
516523
_sb.Append(value.ToString());
517524
}
518525
}

Driver/Linq/Translators/SelectQuery.cs

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,12 @@ private IMongoQuery BuildComparisonQuery(BinaryExpression binaryExpression)
396396
return query;
397397
}
398398

399+
query = BuildTypeComparisonQuery(variableExpression, operatorType, constantExpression);
400+
if (query != null)
401+
{
402+
return query;
403+
}
404+
399405
return BuildComparisonQuery(variableExpression, operatorType, constantExpression);
400406
}
401407

@@ -1251,6 +1257,74 @@ private IMongoQuery BuildStringQuery(MethodCallExpression methodCallExpression)
12511257
return null;
12521258
}
12531259

1260+
private IMongoQuery BuildTypeComparisonQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
1261+
{
1262+
if (operatorType != ExpressionType.Equal)
1263+
{
1264+
// TODO: support NotEqual?
1265+
return null;
1266+
}
1267+
1268+
if (constantExpression.Type != typeof(Type))
1269+
{
1270+
return null;
1271+
}
1272+
var actualType = (Type)constantExpression.Value;
1273+
1274+
var methodCallExpression = variableExpression as MethodCallExpression;
1275+
if (methodCallExpression == null)
1276+
{
1277+
return null;
1278+
}
1279+
if (methodCallExpression.Method.Name != "GetType" || methodCallExpression.Object == null)
1280+
{
1281+
return null;
1282+
}
1283+
if (methodCallExpression.Arguments.Count != 0)
1284+
{
1285+
return null;
1286+
}
1287+
1288+
// TODO: would the object ever not be a ParameterExpression?
1289+
var parameterExpression = methodCallExpression.Object as ParameterExpression;
1290+
if (parameterExpression == null)
1291+
{
1292+
return null;
1293+
}
1294+
1295+
var serializationInfo = GetSerializationInfo(parameterExpression);
1296+
if (serializationInfo == null)
1297+
{
1298+
return null;
1299+
}
1300+
var nominalType = serializationInfo.NominalType;
1301+
1302+
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
1303+
var discriminator = discriminatorConvention.GetDiscriminator(nominalType, actualType);
1304+
if (discriminator == null)
1305+
{
1306+
return new QueryDocument(); // matches everything
1307+
}
1308+
1309+
if (discriminator.IsBsonArray)
1310+
{
1311+
var discriminatorArray = discriminator.AsBsonArray;
1312+
var queries = new IMongoQuery[discriminatorArray.Count + 1];
1313+
queries[0] = Query.Size(discriminatorConvention.ElementName, discriminatorArray.Count);
1314+
for (var i = 0; i < discriminatorArray.Count; i++)
1315+
{
1316+
queries[i + 1] = Query.EQ(string.Format("{0}.{1}", discriminatorConvention.ElementName, i), discriminatorArray[i]);
1317+
}
1318+
return Query.And(queries);
1319+
}
1320+
else
1321+
{
1322+
return Query.And(
1323+
Query.Exists(discriminatorConvention.ElementName + ".0", false), // trick to check that element is not an array
1324+
Query.EQ(discriminatorConvention.ElementName, discriminator));
1325+
}
1326+
}
1327+
12541328
private IMongoQuery BuildTypeIsQuery(TypeBinaryExpression typeBinaryExpression)
12551329
{
12561330
var nominalType = typeBinaryExpression.Expression.Type;
@@ -1260,7 +1334,7 @@ private IMongoQuery BuildTypeIsQuery(TypeBinaryExpression typeBinaryExpression)
12601334
var discriminator = discriminatorConvention.GetDiscriminator(nominalType, actualType);
12611335
if (discriminator == null)
12621336
{
1263-
return Query.Not("_").Mod(1, 2); // best query I could come up with that's always true
1337+
return new QueryDocument(); // matches everything
12641338
}
12651339

12661340
if (discriminator.IsBsonArray)
@@ -1355,10 +1429,26 @@ private object ExecuteDistinct(IMongoQuery query)
13551429

13561430
private BsonSerializationInfo GetSerializationInfo(Expression expression)
13571431
{
1432+
var parameterExpression = expression as ParameterExpression;
1433+
if (parameterExpression != null)
1434+
{
1435+
var serializer = BsonSerializer.LookupSerializer(parameterExpression.Type);
1436+
return new BsonSerializationInfo(
1437+
null, // elementName
1438+
serializer,
1439+
parameterExpression.Type, // nominalType
1440+
null); // serialization options
1441+
}
1442+
13581443
// when using OfType the documentType used by the parameter might be a subclass of the DocumentType from the collection
1359-
var parameterExpression = ExpressionParameterFinder.FindParameter(expression);
1360-
var documentSerializer = BsonSerializer.LookupSerializer(parameterExpression.Type);
1361-
return GetSerializationInfo(documentSerializer, expression);
1444+
parameterExpression = ExpressionParameterFinder.FindParameter(expression);
1445+
if (parameterExpression != null)
1446+
{
1447+
var serializer = BsonSerializer.LookupSerializer(parameterExpression.Type);
1448+
return GetSerializationInfo(serializer, expression);
1449+
}
1450+
1451+
return null;
13621452
}
13631453

13641454
private BsonSerializationInfo GetSerializationInfo(IBsonSerializer serializer, Expression expression)

DriverUnitTests/Builders/QueryBuilderTests.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,36 @@ public void TestAndNullSecond()
9797
Assert.IsTrue(ex.Message.StartsWith("One of the queries is null."));
9898
}
9999

100+
[Test]
101+
public void TestAndWithEmptyQuery()
102+
{
103+
var emptyQuery = new QueryDocument();
104+
105+
var query = Query.And(emptyQuery);
106+
var expected = "{ }";
107+
Assert.AreEqual(expected, query.ToJson());
108+
109+
query = Query.And(emptyQuery, emptyQuery);
110+
expected = "{ }";
111+
Assert.AreEqual(expected, query.ToJson());
112+
113+
query = Query.And(emptyQuery, Query.EQ("x", 1));
114+
expected = "{ \"x\" : 1 }";
115+
Assert.AreEqual(expected, query.ToJson());
116+
117+
query = Query.And(Query.EQ("x", 1), emptyQuery);
118+
expected = "{ \"x\" : 1 }";
119+
Assert.AreEqual(expected, query.ToJson());
120+
121+
query = Query.And(emptyQuery, Query.EQ("x", 1), emptyQuery);
122+
expected = "{ \"x\" : 1 }";
123+
Assert.AreEqual(expected, query.ToJson());
124+
125+
query = Query.And(Query.EQ("x", 1), emptyQuery, Query.EQ("y", 2));
126+
expected = "{ \"x\" : 1, \"y\" : 2 }";
127+
Assert.AreEqual(expected, query.ToJson());
128+
}
129+
100130
[Test]
101131
public void TestElementMatch()
102132
{
@@ -387,6 +417,36 @@ public void TestOrNullSecond()
387417
Assert.IsTrue(ex.Message.StartsWith("One of the queries is null."));
388418
}
389419

420+
[Test]
421+
public void TestOrWithEmptyQuery()
422+
{
423+
var emptyQuery = new QueryDocument();
424+
425+
var query = Query.Or(emptyQuery);
426+
var expected = "{ }";
427+
Assert.AreEqual(expected, query.ToJson());
428+
429+
query = Query.Or(emptyQuery, emptyQuery);
430+
expected = "{ }";
431+
Assert.AreEqual(expected, query.ToJson());
432+
433+
query = Query.Or(emptyQuery, Query.EQ("x", 1));
434+
expected = "{ \"x\" : 1 }";
435+
Assert.AreEqual(expected, query.ToJson());
436+
437+
query = Query.Or(Query.EQ("x", 1), emptyQuery);
438+
expected = "{ \"x\" : 1 }";
439+
Assert.AreEqual(expected, query.ToJson());
440+
441+
query = Query.Or(emptyQuery, Query.EQ("x", 1), emptyQuery);
442+
expected = "{ \"x\" : 1 }";
443+
Assert.AreEqual(expected, query.ToJson());
444+
445+
query = Query.Or(Query.EQ("x", 1), emptyQuery, Query.EQ("y", 2));
446+
expected = "{ \"$or\" : [{ \"x\" : 1 }, { \"y\" : 2 }] }";
447+
Assert.AreEqual(expected, query.ToJson());
448+
}
449+
390450
[Test]
391451
public void TestRegex()
392452
{

DriverUnitTests/Linq/SelectOfTypeHierarchicalTests.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,81 @@ where b is D
249249
Assert.AreEqual(1, Consume(query));
250250
}
251251

252+
[Test]
253+
public void TestWhereBTypeEqualsB()
254+
{
255+
var query =
256+
from b in _collection.AsQueryable<B>()
257+
where b.GetType() == typeof(B)
258+
select b;
259+
260+
var translatedQuery = MongoQueryTranslator.Translate(query);
261+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
262+
Assert.AreSame(_collection, translatedQuery.Collection);
263+
Assert.AreSame(typeof(B), translatedQuery.DocumentType);
264+
265+
var selectQuery = (SelectQuery)translatedQuery;
266+
Assert.AreEqual("(B b) => (b.GetType() == typeof(B))", ExpressionFormatter.ToString(selectQuery.Where));
267+
Assert.AreEqual(null, selectQuery.OfType); // OfType ignored because <T> was the same as <TDocument>
268+
Assert.IsNull(selectQuery.OrderBy);
269+
Assert.IsNull(selectQuery.Projection);
270+
Assert.IsNull(selectQuery.Skip);
271+
Assert.IsNull(selectQuery.Take);
272+
273+
Assert.AreEqual("{ \"_t.0\" : { \"$exists\" : false }, \"_t\" : \"B\" }", selectQuery.BuildQuery().ToJson());
274+
Assert.AreEqual(1, Consume(query));
275+
}
276+
277+
[Test]
278+
public void TestWhereBTypeEqualsC()
279+
{
280+
var query =
281+
from b in _collection.AsQueryable<B>()
282+
where b.GetType() == typeof(C)
283+
select b;
284+
285+
var translatedQuery = MongoQueryTranslator.Translate(query);
286+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
287+
Assert.AreSame(_collection, translatedQuery.Collection);
288+
Assert.AreSame(typeof(B), translatedQuery.DocumentType);
289+
290+
var selectQuery = (SelectQuery)translatedQuery;
291+
Assert.AreEqual("(B b) => (b.GetType() == typeof(C))", ExpressionFormatter.ToString(selectQuery.Where));
292+
Assert.AreEqual(null, selectQuery.OfType);
293+
Assert.IsNull(selectQuery.OrderBy);
294+
Assert.IsNull(selectQuery.Projection);
295+
Assert.IsNull(selectQuery.Skip);
296+
Assert.IsNull(selectQuery.Take);
297+
298+
Assert.AreEqual("{ \"_t\" : { \"$size\" : 2 }, \"_t.0\" : \"B\", \"_t.1\" : \"C\" }", selectQuery.BuildQuery().ToJson());
299+
Assert.AreEqual(1, Consume(query));
300+
}
301+
302+
[Test]
303+
public void TestWhereBTypeEqualsD()
304+
{
305+
var query =
306+
from b in _collection.AsQueryable<B>()
307+
where b.GetType() == typeof(D)
308+
select b;
309+
310+
var translatedQuery = MongoQueryTranslator.Translate(query);
311+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
312+
Assert.AreSame(_collection, translatedQuery.Collection);
313+
Assert.AreSame(typeof(B), translatedQuery.DocumentType);
314+
315+
var selectQuery = (SelectQuery)translatedQuery;
316+
Assert.AreEqual("(B b) => (b.GetType() == typeof(D))", ExpressionFormatter.ToString(selectQuery.Where));
317+
Assert.AreEqual(null, selectQuery.OfType);
318+
Assert.IsNull(selectQuery.OrderBy);
319+
Assert.IsNull(selectQuery.Projection);
320+
Assert.IsNull(selectQuery.Skip);
321+
Assert.IsNull(selectQuery.Take);
322+
323+
Assert.AreEqual("{ \"_t\" : { \"$size\" : 3 }, \"_t.0\" : \"B\", \"_t.1\" : \"C\", \"_t.2\" : \"D\" }", selectQuery.BuildQuery().ToJson());
324+
Assert.AreEqual(1, Consume(query));
325+
}
326+
252327
private int Consume<T>(IQueryable<T> query)
253328
{
254329
var count = 0;

0 commit comments

Comments
 (0)