[IMP] osv: use iteration for expression negating
The current code when applying negative operator on an expression used recursion which in extreme case is not best friend with python. e.g: on instance with a lot of wharehouse, some simple action could lead to a domain with lot of elements which could easiliy go over the python maximum recursion limit. This commit fixes this by replacing recursion with iteration. We have a stack of negation flags and loop on each token of the domain as follow : - when we iterate on a leaf, it consumes the top negation flag, - after a '!' operator, the top token negation is inversed, - after an '&' or '|' operator, the top negation flag is duplicated on the top of the stack. closes #9433 opw-653802
This commit is contained in:
parent
a23468c845
commit
c28a28e69e
|
@ -165,6 +165,26 @@ TERM_OPERATORS = ('=', '!=', '<=', '<', '>', '>=', '=?', '=like', '=ilike',
|
||||||
# legal in the processed term.
|
# legal in the processed term.
|
||||||
NEGATIVE_TERM_OPERATORS = ('!=', 'not like', 'not ilike', 'not in')
|
NEGATIVE_TERM_OPERATORS = ('!=', 'not like', 'not ilike', 'not in')
|
||||||
|
|
||||||
|
# Negation of domain expressions
|
||||||
|
DOMAIN_OPERATORS_NEGATION = {
|
||||||
|
AND_OPERATOR: OR_OPERATOR,
|
||||||
|
OR_OPERATOR: AND_OPERATOR,
|
||||||
|
}
|
||||||
|
TERM_OPERATORS_NEGATION = {
|
||||||
|
'<': '>=',
|
||||||
|
'>': '<=',
|
||||||
|
'<=': '>',
|
||||||
|
'>=': '<',
|
||||||
|
'=': '!=',
|
||||||
|
'!=': '=',
|
||||||
|
'in': 'not in',
|
||||||
|
'like': 'not like',
|
||||||
|
'ilike': 'not ilike',
|
||||||
|
'not in': 'in',
|
||||||
|
'not like': 'like',
|
||||||
|
'not ilike': 'ilike',
|
||||||
|
}
|
||||||
|
|
||||||
TRUE_LEAF = (1, '=', 1)
|
TRUE_LEAF = (1, '=', 1)
|
||||||
FALSE_LEAF = (0, '=', 1)
|
FALSE_LEAF = (0, '=', 1)
|
||||||
|
|
||||||
|
@ -261,51 +281,36 @@ def distribute_not(domain):
|
||||||
['|',('user_id','!=',4),('partner_id','not in',[1,2])]
|
['|',('user_id','!=',4),('partner_id','not in',[1,2])]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def negate(leaf):
|
|
||||||
"""Negates and returns a single domain leaf term,
|
|
||||||
using the opposite operator if possible"""
|
|
||||||
left, operator, right = leaf
|
|
||||||
mapping = {
|
|
||||||
'<': '>=',
|
|
||||||
'>': '<=',
|
|
||||||
'<=': '>',
|
|
||||||
'>=': '<',
|
|
||||||
'=': '!=',
|
|
||||||
'!=': '=',
|
|
||||||
}
|
|
||||||
if operator in ('in', 'like', 'ilike'):
|
|
||||||
operator = 'not ' + operator
|
|
||||||
return [(left, operator, right)]
|
|
||||||
if operator in ('not in', 'not like', 'not ilike'):
|
|
||||||
operator = operator[4:]
|
|
||||||
return [(left, operator, right)]
|
|
||||||
if operator in mapping:
|
|
||||||
operator = mapping[operator]
|
|
||||||
return [(left, operator, right)]
|
|
||||||
return [NOT_OPERATOR, (left, operator, right)]
|
|
||||||
|
|
||||||
def distribute_negate(domain):
|
# This is an iterative version of a recursive function that split domain
|
||||||
"""Negate the domain ``subtree`` rooted at domain[0],
|
# into subdomains, processes them and combine the results. The "stack" below
|
||||||
leaving the rest of the domain intact, and return
|
# represents the recursive calls to be done.
|
||||||
(negated_subtree, untouched_domain_rest)
|
result = []
|
||||||
"""
|
stack = [False]
|
||||||
if is_leaf(domain[0]):
|
|
||||||
return negate(domain[0]), domain[1:]
|
for token in domain:
|
||||||
if domain[0] == AND_OPERATOR:
|
negate = stack.pop()
|
||||||
done1, todo1 = distribute_negate(domain[1:])
|
# negate tells whether the subdomain starting with token must be negated
|
||||||
done2, todo2 = distribute_negate(todo1)
|
if is_leaf(token):
|
||||||
return [OR_OPERATOR] + done1 + done2, todo2
|
if negate:
|
||||||
if domain[0] == OR_OPERATOR:
|
left, operator, right = token
|
||||||
done1, todo1 = distribute_negate(domain[1:])
|
if operator in TERM_OPERATORS_NEGATION:
|
||||||
done2, todo2 = distribute_negate(todo1)
|
result.append((left, TERM_OPERATORS_NEGATION[operator], right))
|
||||||
return [AND_OPERATOR] + done1 + done2, todo2
|
else:
|
||||||
if not domain:
|
result.append(NOT_OPERATOR)
|
||||||
return []
|
result.append(token)
|
||||||
if domain[0] != NOT_OPERATOR:
|
else:
|
||||||
return [domain[0]] + distribute_not(domain[1:])
|
result.append(token)
|
||||||
if domain[0] == NOT_OPERATOR:
|
elif token == NOT_OPERATOR:
|
||||||
done, todo = distribute_negate(domain[1:])
|
stack.append(not negate)
|
||||||
return done + distribute_not(todo)
|
elif token in DOMAIN_OPERATORS_NEGATION:
|
||||||
|
result.append(DOMAIN_OPERATORS_NEGATION[token] if negate else token)
|
||||||
|
stack.append(negate)
|
||||||
|
stack.append(negate)
|
||||||
|
else:
|
||||||
|
result.append(token)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
Loading…
Reference in New Issue