From dab23a2dafdb15b935034d42f47f58f13ddfcb83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Voet=20=28ryv=29?= Date: Mon, 25 Oct 2021 14:53:03 +0000 Subject: [PATCH] [IMP] base: improve search on many2one with string values and negative operators Currently, the domain expression on a many2one field with negative operator + string values leads to a extra SQL request on the target model of the many2one which is not optimal and lead to performance degradation. Example: -------- With a simple model A with a one2many (`b_id`) targeting model B (with a field `b_name` and `_rec_name = b_name`): The `A.search([('b_id', 'not like', 'OneName')])` will generate two SQL requests: - A _name_search on model B and returning ids who match `('b_name', 'not like', 'OneName')`. - A other on the model A with the application of the result of the first one `('b_id', 'in', + [False])`. Solution: --------- Avoid the extra SQL request (the first one) and use subquery instead. We do it by translate the `('b_id', 'in', + [False])` expression into `['|', ('b_id', 'in', _subquery), ('b_id', '=', False)]` task-2671476 Part-of: odoo/odoo#78948 --- odoo/addons/base/tests/test_expression.py | 19 ++++----- odoo/osv/expression.py | 47 ++++++++++++----------- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/odoo/addons/base/tests/test_expression.py b/odoo/addons/base/tests/test_expression.py index 28acdb0d4f7..80b391b40ec 100644 --- a/odoo/addons/base/tests/test_expression.py +++ b/odoo/addons/base/tests/test_expression.py @@ -1418,21 +1418,16 @@ class TestMany2one(TransactionCase): ''']): self.Partner.search([('company_id', 'like', self.company.name)]) - with self.assertQueries([ - ''' - SELECT "res_company".id - FROM "res_company" - WHERE (("res_company"."name"::text not like %s) - OR "res_company"."name" IS NULL) - ORDER BY "res_company"."sequence" ,"res_company"."name" - ''', - ''' + with self.assertQueries([''' SELECT "res_partner".id FROM "res_partner" - WHERE (("res_partner"."company_id" IN (%s)) OR "res_partner"."company_id" IS NULL) + WHERE (("res_partner"."company_id" IN ( + SELECT "res_company".id + FROM "res_company" + WHERE (("res_company"."name"::text not like %s) OR "res_company"."name" IS NULL)) + ) OR "res_partner"."company_id" IS NULL) ORDER BY "res_partner"."display_name" - ''' - ]): + ''']): self.Partner.search([('company_id', 'not like', "blablabla")]) diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py index a6861b7dcfc..2f45c5a5057 100644 --- a/odoo/osv/expression.py +++ b/odoo/osv/expression.py @@ -861,30 +861,33 @@ class expression(object): dom = HIERARCHY_FUNCS[operator]('id', ids2, model, parent=left) for dom_leaf in dom: push(dom_leaf, model, alias) - else: - def _get_expression(comodel, left, right, operator): - #Special treatment to ill-formed domains - operator = (operator in ['<', '>', '<=', '>=']) and 'in' or operator - dict_op = {'not in': '!=', 'in': '=', '=': 'in', '!=': 'not in'} - if isinstance(right, tuple): - right = list(right) - if (not isinstance(right, list)) and operator in ['not in', 'in']: - operator = dict_op[operator] - elif isinstance(right, list) and operator in ['!=', '=']: # for domain (FIELD,'=',['value1','value2']) - operator = dict_op[operator] - res_ids = comodel.with_context(active_test=False)._name_search(right, [], operator, limit=None) - if operator in NEGATIVE_TERM_OPERATORS: - res_ids = list(res_ids) + [False] # TODO this should not be appended if False was in 'right' - return left, 'in', res_ids - # resolve string-based m2o criterion into IDs - if isinstance(right, str) or \ - isinstance(right, (tuple, list)) and right and all(isinstance(item, str) for item in right): - push(_get_expression(comodel, left, right, operator), model, alias) + elif ( + isinstance(right, str) + or isinstance(right, (tuple, list)) and right and all(isinstance(item, str) for item in right) + ): + # resolve string-based m2o criterion into IDs subqueries + + # Special treatment to ill-formed domains + operator = 'in' if operator in ('<', '>', '<=', '>=') else operator + dict_op = {'not in': '!=', 'in': '=', '=': 'in', '!=': 'not in'} + if isinstance(right, tuple): + right = list(right) + if not isinstance(right, list) and operator in ('not in', 'in'): + operator = dict_op[operator] + elif isinstance(right, list) and operator in ('!=', '='): # for domain (FIELD,'=',['value1','value2']) + operator = dict_op[operator] + res_ids = comodel.with_context(active_test=False)._name_search(right, [], operator, limit=None) + if operator in NEGATIVE_TERM_OPERATORS: + for dom_leaf in ('|', (left, 'in', res_ids), (left, '=', False)): + push(dom_leaf, model, alias) else: - # right == [] or right == False and all other cases are handled by __leaf_to_sql() - expr, params = self.__leaf_to_sql(leaf, model, alias) - push_result(expr, params) + push((left, 'in', res_ids), model, alias) + + else: + # right == [] or right == False and all other cases are handled by __leaf_to_sql() + expr, params = self.__leaf_to_sql(leaf, model, alias) + push_result(expr, params) # ------------------------------------------------- # BINARY FIELDS STORED IN ATTACHMENT