Q, QCombination: Implement negation (~)
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?
Of course, one could also implement negation as a separate operator and resolve that only on execution