[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.
|
||||
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)
|
||||
FALSE_LEAF = (0, '=', 1)
|
||||
|
||||
|
@ -261,51 +281,36 @@ def distribute_not(domain):
|
|||
['|',('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):
|
||||
"""Negate the domain ``subtree`` rooted at domain[0],
|
||||
leaving the rest of the domain intact, and return
|
||||
(negated_subtree, untouched_domain_rest)
|
||||
"""
|
||||
if is_leaf(domain[0]):
|
||||
return negate(domain[0]), domain[1:]
|
||||
if domain[0] == AND_OPERATOR:
|
||||
done1, todo1 = distribute_negate(domain[1:])
|
||||
done2, todo2 = distribute_negate(todo1)
|
||||
return [OR_OPERATOR] + done1 + done2, todo2
|
||||
if domain[0] == OR_OPERATOR:
|
||||
done1, todo1 = distribute_negate(domain[1:])
|
||||
done2, todo2 = distribute_negate(todo1)
|
||||
return [AND_OPERATOR] + done1 + done2, todo2
|
||||
if not domain:
|
||||
return []
|
||||
if domain[0] != NOT_OPERATOR:
|
||||
return [domain[0]] + distribute_not(domain[1:])
|
||||
if domain[0] == NOT_OPERATOR:
|
||||
done, todo = distribute_negate(domain[1:])
|
||||
return done + distribute_not(todo)
|
||||
# This is an iterative version of a recursive function that split domain
|
||||
# into subdomains, processes them and combine the results. The "stack" below
|
||||
# represents the recursive calls to be done.
|
||||
result = []
|
||||
stack = [False]
|
||||
|
||||
for token in domain:
|
||||
negate = stack.pop()
|
||||
# negate tells whether the subdomain starting with token must be negated
|
||||
if is_leaf(token):
|
||||
if negate:
|
||||
left, operator, right = token
|
||||
if operator in TERM_OPERATORS_NEGATION:
|
||||
result.append((left, TERM_OPERATORS_NEGATION[operator], right))
|
||||
else:
|
||||
result.append(NOT_OPERATOR)
|
||||
result.append(token)
|
||||
else:
|
||||
result.append(token)
|
||||
elif token == NOT_OPERATOR:
|
||||
stack.append(not negate)
|
||||
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