Marten: Negating a predicate that contains an and operator results in an incorrect query

Created on 12 Oct 2020  路  7Comments  路  Source: JasperFx/marten

When negating a predicate that contains an and operator results in an incorrect query.

Example: query.Query<Player>().Where(c => !(c.Name == "Tony" && c.Level == 10)).ToArray();

Result: select d.data, d.id, d.mt_version from public.mt_doc_bug_negating_predicate_with_and_operator_player as d where not CAST(d.data ->> 'Level' as integer) = :arg0

Expected: select d.data, d.id, d.mt_version from public.mt_doc_bug_negating_predicate_with_and_operator_player as d where (not d.data ->> 'Name' = :arg0 or not CAST(d.data ->> 'Level' as integer) = :arg1)

When adding && true to the end of the predicate: query.Query<Player>().Where(c => !(c.Name == "Tony" && c.Level == 10) && true)

Results into: select d.data, d.id, d.mt_version from public.mt_doc_bug_negating_predicate_with_and_operator_player as d where (not d.data ->> 'Name' = :arg0 and not CAST(d.data ->> 'Level' as integer) = :arg1 and true) which is wrong because it should have or operator instead of and.

This is currently broken in 3.13 but works correctly in master.

Repro test here: https://gist.github.com/kasparurban/82e8c1482a19c6a9a43cb74815631c34

bug linq

Most helpful comment

@kasparurban we will take a look and see if we could do a bug fix release on 3.x

All 7 comments

Yikes. The scariest of LINQ related bugs, as they don't yield a runtime exception, but generate a "faulty" (incorrectly translated) but working (syntactically correct) query behind the scenes (not the first of its kind, but luckily there have been only a few [discovered]). Guess these should be given some space in docs or release notes at the very least, since they can pose quite a risk for anybody in prod & with prod data.

Edit: Going from memory, this fails in an area that I've worked with. I'm interested in how it was discovered & how it could have been avoinded @kasparurban (on avoidance, yeah theorem proving gonna be OOS).

We're using specification pattern and specs contain predicates @jokokko.
For example spec IsExpired might contain something like: entity => entity.ValidUntil != null && entity.ValidUntil <= Now.
Specifications can be combined which can result in complex predicates.
Since specifications are also negatable then !IsExpired produces:
entity => !(entity.ValidUntil != null && entity.ValidUntil <= Now).
Taking this into account then simple query like query.Find(!new IsExpired()) would produce incorrect results.

On avoidance, I quess more and better tests wouldn't hurt :). Tests should cover different and complex cases.
The more tests the easier for mistakes like this to be caught.

As I understand this is going to be fixed in version 4 but right now it is a big issue for us.
It can cause unexpected and dangerous behaviour in pretty much everywhere.

We found out that the problematic code is in _WhereClauseVisitor.cs_:

protected override Expression VisitUnary(UnaryExpression node)
{
    switch (node.NodeType)
    {
        case ExpressionType.Not:
            if (node.Operand is SubQueryExpression)
            {
                var nested = new WhereClauseVisitor(_parent, _mapping);
                nested.Visit(node.Operand);

                var @where = new NotWhereFragment(nested.ToWhereFragment());
                _register.Peek()(@where);
            }
            else
            {
                var visitor = new NotVisitor(this, _mapping, _register.Peek(), _parent._serializer);
                visitor.Visit(node);
            }

            return null;
    }

    return base.VisitUnary(node);
}

For example something like this should fix the problem:

if (node.Operand is SubQueryExpression
    || node.Operand.NodeType == ExpressionType.AndAlso
    || node.Operand.NodeType == ExpressionType.OrElse
    || node.Operand.NodeType == ExpressionType.And
    || node.Operand.NodeType == ExpressionType.Or
)

Could someone with a better knowlege of the codebase take a look at this problem?
We would really appreciate if this gets fixed in version 3.

@kasparurban we will take a look and see if we could do a bug fix release on 3.x

Quick note, this issue is not there in 4.x branch and the new linq related implementation changes have sorted this out. I will add the unit tests to 4.x branch.

FYI, bug fix release 3.13.2 is available in NuGet with fixes for this issue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

KopIlya picture KopIlya  路  7Comments

oskardudycz picture oskardudycz  路  4Comments

jeremydmiller picture jeremydmiller  路  6Comments

jokokko picture jokokko  路  9Comments

lahma picture lahma  路  4Comments