From c28a28e69eb9a39a5acfcd1247d87f5bccaca7f3 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 5 Nov 2015 09:18:48 +0100 Subject: [PATCH] [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 --- openerp/osv/expression.py | 93 +++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py index 1e3ac1d0034..a7539dd6108 100644 --- a/openerp/osv/expression.py +++ b/openerp/osv/expression.py @@ -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 # --------------------------------------------------