From 5fcf891e07bf61518e6bf0be0dc4f27859d45778 Mon Sep 17 00:00:00 2001
From: RomainLibert <rli@odoo.com>
Date: Fri, 11 Jan 2019 11:44:49 +0000
Subject: [PATCH] [IMP] various: optimize _name_search

In the overrides of _name_search we should avoid creating domains with
huge lists of ids as it is inefficient.
We can also make sure that we optimize the empty search as in this case
the custom domain doesn't make sense, we can simply search on an empty
domain and, thanks to the limit argument, still keep a fast query.

Linked to task 1918906

Thanks to @odony and @nseinlet

closes odoo/odoo#30155

closes odoo/odoo#30887
---
 addons/account/models/account.py           | 32 +++++++++++++---------
 addons/analytic/models/analytic_account.py | 10 +++----
 addons/purchase/models/purchase.py         |  3 +-
 addons/sale/models/sale.py                 |  4 ++-
 addons/stock/models/stock_location.py      | 10 +++++--
 addons/stock/models/stock_picking.py       |  3 +-
 odoo/addons/base/models/ir_actions.py      |  6 ++--
 odoo/addons/base/models/res_country.py     | 24 ++++++++++------
 odoo/addons/base/models/res_users.py       | 13 +++++----
 9 files changed, 63 insertions(+), 42 deletions(-)

diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index 3687e602f12f..ec97bafd1c60 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -201,7 +201,7 @@ class AccountAccount(models.Model):
             domain = ['|', ('code', '=ilike', name.split(' ')[0] + '%'), ('name', operator, name)]
             if operator in expression.NEGATIVE_TERM_OPERATORS:
                 domain = ['&', '!'] + domain[1:]
-        account_ids = self._search(domain + args, limit=limit, access_rights_uid=name_get_uid)
+        account_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
         return self.browse(account_ids).name_get()
 
     @api.onchange('internal_type')
@@ -382,11 +382,13 @@ class AccountGroup(models.Model):
 
     @api.model
     def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
-        if not args:
-            args = []
-        criteria_operator = ['|'] if operator not in expression.NEGATIVE_TERM_OPERATORS else ['&', '!']
-        domain = criteria_operator + [('code_prefix', '=ilike', name + '%'), ('name', operator, name)]
-        group_ids = self._search(domain + args, limit=limit, access_rights_uid=name_get_uid)
+        args = args or []
+        if operator == 'ilike' and not (name or '').strip():
+            domain = []
+        else:
+            criteria_operator = ['|'] if operator not in expression.NEGATIVE_TERM_OPERATORS else ['&', '!']
+            domain = criteria_operator + [('code_prefix', '=ilike', name + '%'), ('name', operator, name)]
+        group_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
         return self.browse(group_ids).name_get()
 
 
@@ -810,10 +812,13 @@ class AccountJournal(models.Model):
     @api.model
     def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
         args = args or []
-        connector = '|'
-        if operator in expression.NEGATIVE_TERM_OPERATORS:
-            connector = '&'
-        journal_ids = self._search([connector, ('code', operator, name), ('name', operator, name)] + args, limit=limit, access_rights_uid=name_get_uid)
+
+        if operator == 'ilike' and not (name or '').strip():
+            domain = []
+        else:
+            connector = '&' if operator in expression.NEGATIVE_TERM_OPERATORS else '|'
+            domain = [connector, ('code', operator, name), ('name', operator, name)]
+        journal_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
         return self.browse(journal_ids).name_get()
 
     @api.multi
@@ -954,10 +959,11 @@ class AccountTax(models.Model):
             result format: {[(id, name), (id, name), ...]}
         """
         args = args or []
-        if operator in expression.NEGATIVE_TERM_OPERATORS:
-            domain = [('description', operator, name), ('name', operator, name)]
+        if operator == 'ilike' and not (name or '').strip():
+            domain = []
         else:
-            domain = ['|', ('description', operator, name), ('name', operator, name)]
+            connector = '&' if operator in expression.NEGATIVE_TERM_OPERATORS else '|'
+            domain = [connector, ('description', operator, name), ('name', operator, name)]
         tax_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
         return self.browse(tax_ids).name_get()
 
diff --git a/addons/analytic/models/analytic_account.py b/addons/analytic/models/analytic_account.py
index 46fdbb172594..bc88e94a19e0 100644
--- a/addons/analytic/models/analytic_account.py
+++ b/addons/analytic/models/analytic_account.py
@@ -157,11 +157,11 @@ class AccountAnalyticAccount(models.Model):
         if operator not in ('ilike', 'like', '=', '=like', '=ilike'):
             return super(AccountAnalyticAccount, self)._name_search(name, args, operator, limit, name_get_uid=name_get_uid)
         args = args or []
-        domain = ['|', ('code', operator, name), ('name', operator, name)]
-        partners_ids = self.env['res.partner']._search([('name', operator, name)], access_rights_uid=name_get_uid)
-        if partners_ids:
-            domain = ['|'] + domain + [('partner_id', 'in', partners_ids)]
-        analytic_account_ids = self._search(domain + args, limit=limit, access_rights_uid=name_get_uid)
+        if operator == 'ilike' and not (name or '').strip():
+            domain = []
+        else:
+            domain = ['|', '|', ('code', operator, name), ('name', operator, name), ('partner_id.name', operator, name)]
+        analytic_account_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
         return self.browse(analytic_account_ids).name_get()
 
 
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index e72b29d98e4a..f44e04fd70b5 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -5,6 +5,7 @@ from datetime import datetime
 from dateutil.relativedelta import relativedelta
 
 from odoo import api, fields, models, SUPERUSER_ID, _
+from odoo.osv import expression
 from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
 from odoo.tools.float_utils import float_compare
 from odoo.exceptions import UserError, AccessError
@@ -136,7 +137,7 @@ class PurchaseOrder(models.Model):
         domain = []
         if name:
             domain = ['|', ('name', operator, name), ('partner_ref', operator, name)]
-        purchase_order_ids = self._search(domain + args, limit=limit, access_rights_uid=name_get_uid)
+        purchase_order_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
         return self.browse(purchase_order_ids).name_get()
 
     @api.multi
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index dd4bd9cf57e1..2e88fcc3233d 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -439,7 +439,9 @@ class SaleOrder(models.Model):
     @api.model
     def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
         if self._context.get('sale_show_partner_name'):
-            if operator in ('ilike', 'like', '=', '=like', '=ilike'):
+            if operator == 'ilike' and not (name or '').strip():
+                domain = []
+            elif operator in ('ilike', 'like', '=', '=like', '=ilike'):
                 domain = expression.AND([
                     args or [],
                     ['|', ('name', operator, name), ('partner_id.name', operator, name)]
diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py
index 336dbac47bbb..3c95d6695d16 100644
--- a/addons/stock/models/stock_location.py
+++ b/addons/stock/models/stock_location.py
@@ -6,6 +6,7 @@ from dateutil import relativedelta
 from odoo.exceptions import UserError
 
 from odoo import api, fields, models, _
+from odoo.osv import expression
 from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
 
 
@@ -114,9 +115,12 @@ class Location(models.Model):
     @api.model
     def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
         """ search full name and barcode """
-        if args is None:
-            args = []
-        location_ids = self._search(['|', ('barcode', operator, name), ('complete_name', operator, name)] + args, limit=limit, access_rights_uid=name_get_uid)
+        args = args or []
+        if operator == 'ilike' and not (name or '').strip():
+            domain = []
+        else:
+            domain = ['|', ('barcode', operator, name), ('complete_name', operator, name)]
+        location_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
         return self.browse(location_ids).name_get()
 
     def get_putaway_strategy(self, product):
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index 0fbdcf7e7d4a..3929bb157182 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -8,6 +8,7 @@ from datetime import date
 
 from itertools import groupby
 from odoo import api, fields, models, _
+from odoo.osv import expression
 from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
 from odoo.tools.float_utils import float_compare, float_is_zero, float_round
 from odoo.exceptions import UserError
@@ -121,7 +122,7 @@ class PickingType(models.Model):
         domain = []
         if name:
             domain = ['|', ('name', operator, name), ('warehouse_id.name', operator, name)]
-        picking_ids = self._search(domain + args, limit=limit, access_rights_uid=name_get_uid)
+        picking_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
         return self.browse(picking_ids).name_get()
 
     @api.onchange('code')
diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py
index aa64b2074073..1df2eea40604 100644
--- a/odoo/addons/base/models/ir_actions.py
+++ b/odoo/addons/base/models/ir_actions.py
@@ -4,6 +4,7 @@
 import odoo
 from odoo import api, fields, models, tools, SUPERUSER_ID, _
 from odoo.exceptions import MissingError, UserError, ValidationError, AccessError
+from odoo.osv import expression
 from odoo.tools.safe_eval import safe_eval, test_python_expr
 from odoo.tools import pycompat, wrap_module
 from odoo.http import request
@@ -707,10 +708,9 @@ class IrActionsTodo(models.Model):
 
     @api.model
     def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
-        if args is None:
-            args = []
+        args = args or []
         if name:
-            action_ids = self._search([('action_id', operator, name)] + args, limit=limit, access_rights_uid=name_get_uid)
+            action_ids = self._search(expression.AND([[('action_id', operator, name)], args]), limit=limit, access_rights_uid=name_get_uid)
             return self.browse(action_ids).name_get()
         return super(IrActionsTodo, self)._name_search(name, args=args, operator=operator, limit=limit, name_get_uid=name_get_uid)
 
diff --git a/odoo/addons/base/models/res_country.py b/odoo/addons/base/models/res_country.py
index f1bcf72ed3a3..fe86446bfbcc 100644
--- a/odoo/addons/base/models/res_country.py
+++ b/odoo/addons/base/models/res_country.py
@@ -4,6 +4,7 @@
 import re
 import logging
 from odoo import api, fields, models
+from odoo.osv import expression
 from psycopg2 import IntegrityError
 from odoo.tools.translate import _
 _logger = logging.getLogger(__name__)
@@ -119,15 +120,20 @@ class CountryState(models.Model):
 
     @api.model
     def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
-        if args is None:
-            args = []
-        if self.env.context.get('country_id'):
-            args = args + [('country_id', '=', self.env.context.get('country_id'))]
-        first_state_ids = self._search([('code', '=ilike', name)] + args, limit=limit, access_rights_uid=name_get_uid)
-        search_domain = [('name', operator, name)]
-        search_domain.append(('id', 'not in', first_state_ids))
-        state_ids = first_state_ids + self._search(search_domain + args, limit=limit, access_rights_uid=name_get_uid)
-        return [(state.id, state.display_name) for state in self.browse(state_ids)]
+        args = args or []
+        if 'country_id' in self.env.context:
+            args = expression.AND([args, [('country_id', '=', self.env.context.get('country_id'))]])
+
+        if operator == 'ilike' and not (name or '').strip():
+            first_domain = []
+            domain = []
+        else:
+            first_domain = [('code', '=ilike', name)]
+            domain = [('name', operator, name)]
+
+        first_state_ids = self._search(expression.AND([first_domain, args]), limit=limit, access_rights_uid=name_get_uid) if first_domain else []
+        state_ids = first_state_ids + [state_id for state_id in self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid) if not state_id in first_state_ids]
+        return self.browse(state_ids).name_get()
 
     @api.multi
     def name_get(self):
diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py
index 2fb33a527e17..afcf613f7d93 100644
--- a/odoo/addons/base/models/res_users.py
+++ b/odoo/addons/base/models/res_users.py
@@ -489,13 +489,14 @@ class Users(models.Model):
 
     @api.model
     def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
-        if args is None:
-            args = []
-        user_ids = []
-        if name and operator in ['=', 'ilike']:
-            user_ids = self._search([('login', '=', name)] + args, limit=limit, access_rights_uid=name_get_uid)
+        args = args or []
+        if operator == 'ilike' and not (name or '').strip():
+            domain = []
+        else:
+            domain = [('login', '=', name)]
+        user_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
         if not user_ids:
-            user_ids = self._search([('name', operator, name)] + args, limit=limit, access_rights_uid=name_get_uid)
+            user_ids = self._search(expression.AND([[('name', operator, name)], args]), limit=limit, access_rights_uid=name_get_uid)
         return self.browse(user_ids).name_get()
 
     @api.multi
-- 
GitLab