[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', <returning ids> + [False])`.

Solution:
---------
Avoid the extra SQL request (the first one) and use subquery instead.
We do it by translate the `('b_id', 'in', <returning ids> + [False])`
expression into `['|', ('b_id', 'in', _subquery), ('b_id', '=', False)]`

task-2671476

Part-of: odoo/odoo#78948
This commit is contained in:
Rémy Voet (ryv)
2021-10-28 16:08:46 +00:00
committed by Raphael Collet
parent d31234cc5e
commit dab23a2daf
2 changed files with 32 additions and 34 deletions
+7 -12
View File
@@ -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")])
+25 -22
View File
@@ -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