[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:
committed by
Raphael Collet
parent
d31234cc5e
commit
dab23a2daf
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user