mongoengine icon indicating copy to clipboard operation
mongoengine copied to clipboard

Q, QCombination: Implement negation (~)

Open PeterNerlich opened this issue 4 years ago • 1 comments

Q and QCombination can be combined with & and |, which is really cool! I have experimenting with writing a search query parser for a university project, that takes something like (:keyword value :or :whatever else) this and spits out

((Q(**{'keyword': 'value'}) | Q(**{'whatever': 'else'})) & Q(**{'__raw__': {'$text': SON([('$search', 'something like this')])}}))

...which I'm really excited about!

Now I also want to have a NOT operator to search for results without a certain quality, and was surprised mongoengine doesn't implement it, and mongodb only seens to have it per atomic value. Fair enough, but it's easy to implement resolving the negation of a term to an equivalent term where only "leaf nodes" have negations:

¬(A & B) = (¬A | ¬B)

All child expressions are negated and the operation is switched (AND/OR).

I have that already running in my parser using this function:

def negate(atom: Union[Q, QCombination]) -> Union[Q, QCombination]:
    if isinstance(atom, QCombination):
        atom.operation = atom.OR if atom.operation == atom.AND else atom.AND
        for i in range(len(atom.children)):
            atom.children[i] = negate(atom.children[i])
    elif isinstance(atom, Q):
        for k, v in atom.query.items():
            if '$not' in v:
                atom.query[k] = v['$not']
            else:
                atom.query[k] = {'$not': v}
    else:
        raise ValueError('atom neither Q nor QCombination: {}: {}'.format(type(atom), repr(atom)))
    return atom

I could easily see that being implemented for Q/QCombination objects directly for the ~ (bitwise not) operator. Only, I haven't even tested whether the expressions I generate work with mongoengine and will settle with writing this issue for now. I think this is really useful for whoever would want to write really complex queries, what do you think?

PeterNerlich avatar Jul 20 '21 05:07 PeterNerlich

Of course, one could also implement negation as a separate operator and resolve that only on execution

PeterNerlich avatar Jul 20 '21 05:07 PeterNerlich