Skip to content

Commit 7aac675

Browse files
author
rstam
committed
Added more unit tests for C# ! (the not operator) in LINQ queries. One principle is that for a given predicate p then a given document always matches either p or !p but never both. This required being a little less aggressive in simplifying expressions containing the not operator when constructing queries to send to the server (e.g. can't turn not $lt into $gte).
1 parent bcee92c commit 7aac675

File tree

2 files changed

+356
-66
lines changed

2 files changed

+356
-66
lines changed

Driver/Linq/Translators/SelectQuery.cs

Lines changed: 39 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -673,57 +673,52 @@ private IMongoQuery BuildNotQuery(UnaryExpression unaryExpression)
673673
if (queryDocument.ElementCount == 1)
674674
{
675675
var elementName = queryDocument.GetElement(0).Name;
676-
if (elementName == "$or")
676+
switch (elementName)
677677
{
678-
var clauses = queryDocument[0].AsBsonArray;
679-
return new QueryDocument("$nor", clauses);
678+
case "$and":
679+
// there is no $nand and $not only works as a meta operator on a single operator so simulate $not using $nor
680+
return new QueryDocument("$nor", new BsonArray { queryDocument });
681+
case "$or":
682+
return new QueryDocument("$nor", queryDocument[0].AsBsonArray);
683+
case "$nor":
684+
return new QueryDocument("$or", queryDocument[0].AsBsonArray);
680685
}
681686

682687
var operatorDocument = queryDocument[0] as BsonDocument;
683-
if (operatorDocument != null && operatorDocument.ElementCount == 1)
688+
if (operatorDocument != null && operatorDocument.ElementCount > 0)
684689
{
685690
var operatorName = operatorDocument.GetElement(0).Name;
686-
switch (operatorName)
691+
if (operatorDocument.ElementCount == 1)
687692
{
688-
case "$exists":
689-
var boolValue = operatorDocument[0].AsBoolean;
690-
return new QueryDocument(elementName, new BsonDocument("$exists", !boolValue));
691-
case "$in":
692-
var values = operatorDocument[0].AsBsonArray;
693-
return new QueryDocument(elementName, new BsonDocument("$nin", values));
694-
case "$not":
695-
var predicate = operatorDocument[0];
696-
return new QueryDocument(elementName, predicate);
697-
case "$lt":
698-
case "$lte":
699-
case "$ne":
700-
case "$gt":
701-
case "$gte":
702-
string oppositeOperator;
703-
switch (operatorName)
704-
{
705-
case "$lt": oppositeOperator = "$gte"; break;
706-
case "$lte": oppositeOperator = "$gt"; break;
707-
case "$ne": oppositeOperator = "$eq"; break;
708-
case "$gt": oppositeOperator = "$lte"; break;
709-
case "$gte": oppositeOperator = "$lt"; break;
710-
default: throw new InvalidOperationException("Unreachable code.");
711-
}
712-
var comparisonValue = operatorDocument[0];
713-
if (oppositeOperator == "$eq")
714-
{
693+
switch (operatorName)
694+
{
695+
case "$exists":
696+
var boolValue = operatorDocument[0].AsBoolean;
697+
return new QueryDocument(elementName, new BsonDocument("$exists", !boolValue));
698+
case "$in":
699+
var values = operatorDocument[0].AsBsonArray;
700+
return new QueryDocument(elementName, new BsonDocument("$nin", values));
701+
case "$not":
702+
var predicate = operatorDocument[0];
703+
return new QueryDocument(elementName, predicate);
704+
case "$ne":
705+
var comparisonValue = operatorDocument[0];
715706
return new QueryDocument(elementName, comparisonValue);
716-
}
717-
else
718-
{
719-
return new QueryDocument(elementName, new BsonDocument(oppositeOperator, comparisonValue));
720-
}
707+
}
708+
if (operatorName[0] == '$')
709+
{
710+
// use $not as a meta operator on a single operator
711+
return new QueryDocument(elementName, new BsonDocument("$not", operatorDocument));
712+
}
721713
}
722-
723-
// use $not as a meta operator
724-
if (operatorName[0] == '$')
714+
else
725715
{
726-
return new QueryDocument(elementName, new BsonDocument("$not", operatorDocument));
716+
// $ref isn't an operator (it's the first field of a DBRef)
717+
if (operatorName[0] == '$' && operatorName != "$ref")
718+
{
719+
// $not only works as a meta operator on a single operator so simulate $not using $nor
720+
return new QueryDocument("$nor", new BsonArray { queryDocument });
721+
}
727722
}
728723
}
729724

@@ -733,20 +728,11 @@ private IMongoQuery BuildNotQuery(UnaryExpression unaryExpression)
733728
return new QueryDocument(elementName, new BsonDocument("$not", operatorValue));
734729
}
735730

736-
if (operatorValue.IsBoolean)
737-
{
738-
// turn implied boolean test into test against opposite boolean value
739-
var oppositeValue = !operatorValue.AsBoolean;
740-
return new QueryDocument(elementName, oppositeValue);
741-
}
742-
else
743-
{
744-
// turn implied equality comparison into $ne
745-
return new QueryDocument(elementName, new BsonDocument("$ne", operatorValue));
746-
}
731+
// turn implied equality comparison into $ne
732+
return new QueryDocument(elementName, new BsonDocument("$ne", operatorValue));
747733
}
748734

749-
// $not only works as a meta operator so simulate $not using $nor
735+
// $not only works as a meta operator on a single operator so simulate $not using $nor
750736
return new QueryDocument("$nor", new BsonArray { queryDocument });
751737
}
752738

0 commit comments

Comments
 (0)