Skip to content

Commit

Permalink
Fixing negation implementation
Browse files Browse the repository at this point in the history
Negating "AndAlso" isn't the same as negating an "OrElse". The "OrElse" can use a "$nor" whereas the "AndAlso" needs to have the individual operations use "$not" around their operator expression.
  • Loading branch information
Turnerj committed Nov 16, 2019
1 parent 794d8e2 commit bbe1522
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 15 deletions.
39 changes: 24 additions & 15 deletions src/MongoFramework/Infrastructure/Querying/ExpressionTranslation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,30 +215,30 @@ public static Expression GetMemberSource(Expression expression)
return currentExpression;
}

public static BsonDocument TranslateConditional(Expression expression)
public static BsonDocument TranslateConditional(Expression expression, bool negated = false)
{
var localExpression = UnwrapLambda(expression);

static void UnwrapBinaryQuery(BsonArray target, ExpressionType expressionType, BinaryExpression expression)
static void UnwrapBinaryQuery(BsonArray target, ExpressionType expressionType, BinaryExpression expression, bool negated)
{
if (expression.Left.NodeType == expressionType)
{
UnwrapBinaryQuery(target, expressionType, expression.Left as BinaryExpression);
UnwrapBinaryQuery(target, expressionType, expression.Left as BinaryExpression, negated);
}
else
{
target.Add(TranslateSubExpression(expression.Left));
target.Add(TranslateConditional(expression.Left, negated));
}

target.Add(TranslateSubExpression(expression.Right));
target.Add(TranslateConditional(expression.Right, negated));
}

if (localExpression is BinaryExpression binaryExpression)
{
if (localExpression.NodeType == ExpressionType.AndAlso)
{
var unwrappedQuery = new BsonArray();
UnwrapBinaryQuery(unwrappedQuery, ExpressionType.AndAlso, binaryExpression);
UnwrapBinaryQuery(unwrappedQuery, ExpressionType.AndAlso, binaryExpression, negated);

var elements = new BsonElement[unwrappedQuery.Count];

Expand All @@ -252,7 +252,7 @@ static void UnwrapBinaryQuery(BsonArray target, ExpressionType expressionType, B
else if (localExpression.NodeType == ExpressionType.OrElse)
{
var unwrappedQuery = new BsonArray();
UnwrapBinaryQuery(unwrappedQuery, ExpressionType.OrElse, binaryExpression);
UnwrapBinaryQuery(unwrappedQuery, ExpressionType.OrElse, binaryExpression, negated);
return new BsonDocument
{
{ "$or", unwrappedQuery }
Expand Down Expand Up @@ -289,25 +289,34 @@ static void UnwrapBinaryQuery(BsonArray target, ExpressionType expressionType, B

var expressionOperator = ComparatorToStringMap[expressionType];
var valueComparison = new BsonDocument { { expressionOperator, value } };

if (negated)
{
valueComparison = new BsonDocument
{
{ "$not", valueComparison }
};
}

return new BsonDocument { { fieldName, valueComparison } };
}
}
else if (localExpression is UnaryExpression unaryExpression && unaryExpression.NodeType == ExpressionType.Not)
{
string operatorName;
if (unaryExpression.Operand.NodeType == ExpressionType.OrElse)
{
operatorName = "$nor";
var translatedInnerExpression = TranslateConditional(unaryExpression.Operand, false);
var valueItems = translatedInnerExpression.GetElement("$or").Value;

return new BsonDocument
{
{ "$nor", valueItems }
};
}
else
{
operatorName = "$not";
return TranslateConditional(unaryExpression.Operand, !negated);
}

return new BsonDocument
{
{ operatorName, TranslateConditional(unaryExpression.Operand) }
};
}

throw new ArgumentException($"Unexpected node type {expression.NodeType} for a conditional statement");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,69 @@ public void TranslateConditional_GreaterThanOrEqual()
};
Assert.AreEqual(expected, result);
}

[TestMethod]
public void TranslateConditional_AndAlso()
{
var expression = GetConditional(e => e.Id == "" && e.SingleNumber >= 5);
var result = ExpressionTranslation.TranslateConditional(expression);
var expected = new BsonDocument
{
{ "Id", new BsonDocument { { "$eq", "" } } },
{ "SingleNumber", new BsonDocument { { "$gte", 5 } } }
};
Assert.AreEqual(expected, result);
}

[TestMethod]
public void TranslateConditional_OrElse()
{
var expression = GetConditional(e => e.Id == "" || e.SingleNumber >= 5);
var result = ExpressionTranslation.TranslateConditional(expression);
var expected = new BsonDocument
{
{
"$or",
new BsonArray
{
new BsonDocument { { "Id", new BsonDocument { { "$eq", "" } } } },
new BsonDocument { { "SingleNumber", new BsonDocument { { "$gte", 5 } } } }
}
}
};
Assert.AreEqual(expected, result);
}

[TestMethod]
public void TranslateConditional_Not_AndAlso()
{
var expression = GetConditional(e => !(e.Id == "" && e.SingleNumber >= 5));
var result = ExpressionTranslation.TranslateConditional(expression);
var expected = new BsonDocument
{
{ "Id", new BsonDocument { { "$not", new BsonDocument { { "$eq", "" } } } } },
{ "SingleNumber", new BsonDocument { { "$not", new BsonDocument { { "$gte", 5 } } } } }
};
Assert.AreEqual(expected, result);
}

[TestMethod]
public void TranslateConditional_Not_OrElse()
{
var expression = GetConditional(e => !(e.Id == "" || e.SingleNumber >= 5));
var result = ExpressionTranslation.TranslateConditional(expression);
var expected = new BsonDocument
{
{
"$nor",
new BsonArray
{
new BsonDocument { { "Id", new BsonDocument { { "$eq", "" } } } },
new BsonDocument { { "SingleNumber", new BsonDocument { { "$gte", 5 } } } }
}
}
};
Assert.AreEqual(expected, result);
}
}
}

0 comments on commit bbe1522

Please sign in to comment.